Merge with 2.0.0-m3

This commit is contained in:
René Pfeuffer
2018-12-19 11:10:27 +01:00
61 changed files with 863 additions and 238 deletions

View File

@@ -0,0 +1,9 @@
package sonia.scm;
import java.util.List;
public abstract class BadRequestException extends ExceptionWithContext {
public BadRequestException(List<ContextEntry> context, String message) {
super(context, message);
}
}

View File

@@ -40,13 +40,14 @@ import java.util.Collections;
* @author Sebastian Sdorra
* @version 1.6
*/
public class NotSupportedFeatureException extends ExceptionWithContext {
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
public class FeatureNotSupportedException extends BadRequestException {
private static final long serialVersionUID = 256498734456613496L;
private static final String CODE = "9SR8G0kmU1";
public NotSupportedFeatureException(String feature)
public FeatureNotSupportedException(String feature)
{
super(Collections.emptyList(),createMessage(feature));
}

View File

@@ -38,7 +38,7 @@ package sonia.scm.repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.SCMContextProvider;
import sonia.scm.event.ScmEventBus;
@@ -167,12 +167,12 @@ public abstract class AbstractRepositoryHandler<C extends RepositoryConfig>
*
* @return
*
* @throws NotSupportedFeatureException
* @throws FeatureNotSupportedException
*/
@Override
public ImportHandler getImportHandler()
{
throw new NotSupportedFeatureException("import");
throw new FeatureNotSupportedException("import");
}
/**

View File

@@ -36,7 +36,7 @@ package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.Handler;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.plugin.ExtensionPoint;
/**
@@ -59,9 +59,9 @@ public interface RepositoryHandler
* @return {@link ImportHandler} for the repository type of this handler
* @since 1.12
*
* @throws NotSupportedFeatureException
* @throws FeatureNotSupportedException
*/
public ImportHandler getImportHandler() throws NotSupportedFeatureException;
public ImportHandler getImportHandler() throws FeatureNotSupportedException;
/**
* Returns informations about the version of the RepositoryHandler.

View File

@@ -38,7 +38,7 @@ package sonia.scm.repository.api;
import com.google.common.base.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.repository.Feature;
import sonia.scm.repository.spi.DiffCommand;
import sonia.scm.repository.spi.DiffCommandRequest;
@@ -203,7 +203,7 @@ public final class DiffCommandBuilder
public DiffCommandBuilder setAncestorChangeset(String revision)
{
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
throw new NotSupportedFeatureException(Feature.INCOMING_REVISION.name());
throw new FeatureNotSupportedException(Feature.INCOMING_REVISION.name());
}
request.setAncestorChangeset(revision);

View File

@@ -39,7 +39,7 @@ import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.cache.Cache;
import sonia.scm.cache.CacheManager;
import sonia.scm.repository.Changeset;
@@ -410,7 +410,7 @@ public final class LogCommandBuilder
*/
public LogCommandBuilder setAncestorChangeset(String ancestorChangeset) {
if (!supportedFeatures.contains(Feature.INCOMING_REVISION)) {
throw new NotSupportedFeatureException(Feature.INCOMING_REVISION.name());
throw new FeatureNotSupportedException(Feature.INCOMING_REVISION.name());
}
request.setAncestorChangeset(ancestorChangeset);
return this;

View File

@@ -80,8 +80,20 @@ public interface AccessToken {
*/
Date getExpiration();
/**
* Returns refresh expiration of token.
*
* @return refresh expiration
*/
Optional<Date> getRefreshExpiration();
/**
* Returns id of the parent key.
*
* @return parent key id
*/
Optional<String> getParentKey();
/**
* Returns the scope of the token. The scope is able to reduce the permissions of the subject in the context of this
* token. For example we could issue a token which can only be used to read a single repository. for more informations

View File

@@ -0,0 +1,30 @@
package sonia.scm.security;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Generates cookies and invalidates access token cookies.
*
* @author Sebastian Sdorra
* @since 2.0.0
*/
public interface AccessTokenCookieIssuer {
/**
* Creates a cookie for token authentication and attaches it to the response.
*
* @param request http servlet request
* @param response http servlet response
* @param accessToken access token
*/
void authenticate(HttpServletRequest request, HttpServletResponse response, AccessToken accessToken);
/**
* Invalidates the authentication cookie.
*
* @param request http servlet request
* @param response http servlet response
*/
void invalidate(HttpServletRequest request, HttpServletResponse response);
}

View File

@@ -164,7 +164,7 @@ public class DefaultCipherHandler implements CipherHandler {
String result = null;
try {
byte[] encodedInput = Base64.getDecoder().decode(value);
byte[] encodedInput = Base64.getUrlDecoder().decode(value);
byte[] salt = new byte[SALT_LENGTH];
byte[] encoded = new byte[encodedInput.length - SALT_LENGTH];
@@ -221,7 +221,7 @@ public class DefaultCipherHandler implements CipherHandler {
System.arraycopy(salt, 0, result, 0, SALT_LENGTH);
System.arraycopy(encodedInput, 0, result, SALT_LENGTH,
result.length - SALT_LENGTH);
res = new String(Base64.getEncoder().encode(result), ENCODING);
res = new String(Base64.getUrlEncoder().encode(result), ENCODING);
} catch (IOException | GeneralSecurityException ex) {
throw new CipherException("could not encode string", ex);
}

View File

@@ -33,6 +33,10 @@
package sonia.scm.store;
import java.util.Optional;
import static java.util.Optional.ofNullable;
/**
* ConfigurationStore for configuration objects. <strong>Note:</strong> the default
* implementation use JAXB to marshall the configuration objects.
@@ -50,7 +54,17 @@ public interface ConfigurationStore<T>
*
* @return configuration object from store
*/
public T get();
T get();
/**
* Returns the configuration object from store.
*
*
* @return configuration object from store
*/
default Optional<T> getOptional() {
return ofNullable(get());
}
//~--- set methods ----------------------------------------------------------
@@ -60,5 +74,5 @@ public interface ConfigurationStore<T>
*
* @param obejct configuration object to store
*/
public void set(T obejct);
void set(T object);
}

View File

@@ -32,6 +32,10 @@
package sonia.scm.store;
import java.util.Optional;
import static java.util.Optional.ofNullable;
/**
* Base class for {@link BlobStore} and {@link DataStore}.
*
@@ -67,4 +71,16 @@ public interface MultiEntryStore<T> {
* @return item with the given id
*/
public T get(String id);
/**
* Returns the item with the given id from the store.
*
*
* @param id id of the item to return
*
* @return item with the given id
*/
default Optional<T> getOptional(String id) {
return ofNullable(get(id));
}
}

View File

@@ -1,12 +1,13 @@
package sonia.scm.user;
import sonia.scm.BadRequestException;
import sonia.scm.ContextEntry;
import sonia.scm.ExceptionWithContext;
public class ChangePasswordNotAllowedException extends ExceptionWithContext {
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
public class ChangePasswordNotAllowedException extends BadRequestException {
private static final String CODE = "9BR7qpDAe1";
public static final String WRONG_USER_TYPE = "User of type %s are not allowed to change password";
public static final String WRONG_USER_TYPE = "Users of type %s are not allowed to change password";
public ChangePasswordNotAllowedException(ContextEntry.ContextBuilder context, String type) {
super(context.build(), String.format(WRONG_USER_TYPE, type));

View File

@@ -1,9 +1,10 @@
package sonia.scm.user;
import sonia.scm.BadRequestException;
import sonia.scm.ContextEntry;
import sonia.scm.ExceptionWithContext;
public class InvalidPasswordException extends ExceptionWithContext {
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
public class InvalidPasswordException extends BadRequestException {
private static final String CODE = "8YR7aawFW1";

View File

@@ -0,0 +1,40 @@
package sonia.scm.web;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import static java.util.Collections.singletonMap;
import static sonia.scm.web.VndMediaType.REPOSITORY;
import static sonia.scm.web.VndMediaType.REPOSITORY_COLLECTION;
public abstract class AbstractRepositoryJsonEnricher extends JsonEnricherBase {
public AbstractRepositoryJsonEnricher(ObjectMapper objectMapper) {
super(objectMapper);
}
@Override
public void enrich(JsonEnricherContext context) {
if (resultHasMediaType(REPOSITORY, context)) {
JsonNode repositoryNode = context.getResponseEntity();
enrichRepositoryNode(repositoryNode);
} else if (resultHasMediaType(REPOSITORY_COLLECTION, context)) {
JsonNode repositoryCollectionNode = context.getResponseEntity().get("_embedded").withArray("repositories");
repositoryCollectionNode.elements().forEachRemaining(this::enrichRepositoryNode);
}
}
private void enrichRepositoryNode(JsonNode repositoryNode) {
String namespace = repositoryNode.get("namespace").asText();
String name = repositoryNode.get("name").asText();
enrichRepositoryNode(repositoryNode, namespace, name);
}
protected abstract void enrichRepositoryNode(JsonNode repositoryNode, String namespace, String name);
protected void addLink(JsonNode repositoryNode, String linkName, String link) {
JsonNode hrefNode = createObject(singletonMap("href", value(link)));
addPropertyNode(repositoryNode.get("_links"), linkName, hrefNode);
}
}

View File

@@ -0,0 +1,25 @@
package sonia.scm.xml;
import javax.xml.bind.annotation.adapters.XmlAdapter;
import java.time.Instant;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
/**
* JAXB adapter for {@link Instant} objects.
*
* @since 2.0.0
*/
public class XmlInstantAdapter extends XmlAdapter<String, Instant> {
@Override
public String marshal(Instant instant) {
return DateTimeFormatter.ISO_INSTANT.format(instant);
}
@Override
public Instant unmarshal(String text) {
TemporalAccessor parsed = DateTimeFormatter.ISO_INSTANT.parse(text);
return Instant.from(parsed);
}
}

View File

@@ -0,0 +1,107 @@
package sonia.scm.web;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.Resources;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import sonia.scm.api.v2.resources.ScmPathInfoStore;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.net.URI;
import java.net.URL;
import static org.assertj.core.api.Assertions.assertThat;
@ExtendWith(MockitoExtension.class)
class AbstractRepositoryJsonEnricherTest {
private final ObjectMapper objectMapper = new ObjectMapper();
private AbstractRepositoryJsonEnricher linkEnricher;
private JsonNode rootNode;
@BeforeEach
void globalSetUp() {
ScmPathInfoStore pathInfoStore = new ScmPathInfoStore();
pathInfoStore.set(() -> URI.create("/"));
linkEnricher = new AbstractRepositoryJsonEnricher(objectMapper) {
@Override
protected void enrichRepositoryNode(JsonNode repositoryNode, String namespace, String name) {
addLink(repositoryNode, "new-link", "/somewhere");
}
};
}
@Test
void shouldEnrichRepositories() throws IOException {
URL resource = Resources.getResource("sonia/scm/repository/repository-001.json");
rootNode = objectMapper.readTree(resource);
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.REPOSITORY),
rootNode
);
linkEnricher.enrich(context);
String configLink = context.getResponseEntity()
.get("_links")
.get("new-link")
.get("href")
.asText();
assertThat(configLink).isEqualTo("/somewhere");
}
@Test
void shouldEnrichAllRepositories() throws IOException {
URL resource = Resources.getResource("sonia/scm/repository/repository-collection-001.json");
rootNode = objectMapper.readTree(resource);
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.REPOSITORY_COLLECTION),
rootNode
);
linkEnricher.enrich(context);
context.getResponseEntity()
.get("_embedded")
.withArray("repositories")
.elements()
.forEachRemaining(node -> {
String configLink = node
.get("_links")
.get("new-link")
.get("href")
.asText();
assertThat(configLink).isEqualTo("/somewhere");
});
}
@Test
void shouldNotModifyObjectsWithUnsupportedMediaType() throws IOException {
URL resource = Resources.getResource("sonia/scm/repository/repository-001.json");
rootNode = objectMapper.readTree(resource);
JsonEnricherContext context = new JsonEnricherContext(
URI.create("/"),
MediaType.valueOf(VndMediaType.USER),
rootNode
);
linkEnricher.enrich(context);
boolean hasNewPullRequestLink = context.getResponseEntity()
.get("_links")
.has("new-link");
assertThat(hasNewPullRequestLink).isFalse();
}
}

View File

@@ -0,0 +1,47 @@
package sonia.scm.xml;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import javax.xml.bind.JAXB;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.nio.file.Path;
import java.time.Instant;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(TempDirectory.class)
class XmlInstantAdapterTest {
@Test
void shouldMarshalAndUnmarshalInstant(@TempDirectory.TempDir Path tempDirectory) {
Path path = tempDirectory.resolve("instant.xml");
Instant instant = Instant.now();
InstantObject object = new InstantObject(instant);
JAXB.marshal(object, path.toFile());
InstantObject unmarshaled = JAXB.unmarshal(path.toFile(), InstantObject.class);
assertEquals(instant, unmarshaled.instant);
}
@XmlRootElement(name = "instant-object")
@XmlAccessorType(XmlAccessType.FIELD)
public static class InstantObject {
@XmlJavaTypeAdapter(XmlInstantAdapter.class)
private Instant instant;
public InstantObject() {
}
InstantObject(Instant instant) {
this.instant = instant;
}
}
}

View File

@@ -0,0 +1,42 @@
{
"creationDate": "2018-11-09T09:48:32.732Z",
"description": "Handling static webresources made easy",
"healthCheckFailures": [],
"lastModified": "2018-11-09T09:49:20.973Z",
"namespace": "scmadmin",
"name": "web-resources",
"archived": false,
"type": "git",
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"delete": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"update": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"permissions": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/permissions/"
},
"protocol": [
{
"href": "http://localhost:8081/scm/repo/scmadmin/web-resources",
"name": "http"
}
],
"tags": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/tags/"
},
"branches": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/branches/"
},
"changesets": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/changesets/"
},
"sources": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/sources/"
}
}
}

View File

@@ -0,0 +1,106 @@
{
"page": 0,
"pageTotal": 1,
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/?page=0&pageSize=10"
},
"first": {
"href": "http://localhost:8081/scm/api/v2/repositories/?page=0&pageSize=10"
},
"last": {
"href": "http://localhost:8081/scm/api/v2/repositories/?page=0&pageSize=10"
},
"create": {
"href": "http://localhost:8081/scm/api/v2/repositories/"
}
},
"_embedded": {
"repositories": [
{
"creationDate": "2018-11-09T09:48:32.732Z",
"description": "Handling static webresources made easy",
"healthCheckFailures": [],
"lastModified": "2018-11-09T09:49:20.973Z",
"namespace": "scmadmin",
"name": "web-resources",
"archived": false,
"type": "git",
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"delete": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"update": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"permissions": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/permissions/"
},
"protocol": [
{
"href": "http://localhost:8081/scm/repo/scmadmin/web-resources",
"name": "http"
}
],
"tags": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/tags/"
},
"branches": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/branches/"
},
"changesets": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/changesets/"
},
"sources": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/sources/"
}
}
},
{
"creationDate": "2018-11-09T09:48:32.732Z",
"description": "Handling static webresources made easy",
"healthCheckFailures": [],
"lastModified": "2018-11-09T09:49:20.973Z",
"namespace": "scmadmin",
"name": "web-resources",
"archived": false,
"type": "git",
"_links": {
"self": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"delete": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"update": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources"
},
"permissions": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/permissions/"
},
"protocol": [
{
"href": "http://localhost:8081/scm/repo/scmadmin/web-resources",
"name": "http"
}
],
"tags": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/tags/"
},
"branches": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/branches/"
},
"changesets": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/changesets/"
},
"sources": {
"href": "http://localhost:8081/scm/api/v2/repositories/scmadmin/web-resources/sources/"
}
}
}
]
}
}

View File

@@ -58,8 +58,6 @@ public abstract class FileBasedStoreFactory {
private RepositoryLocationResolver repositoryLocationResolver;
private Store store;
private File storeDirectory;
protected FileBasedStoreFactory(SCMContextProvider contextProvider , RepositoryLocationResolver repositoryLocationResolver, Store store) {
this.contextProvider = contextProvider;
this.repositoryLocationResolver = repositoryLocationResolver;
@@ -75,7 +73,7 @@ public abstract class FileBasedStoreFactory {
}
protected File getStoreLocation(String name, Class type, Repository repository) {
if (storeDirectory == null) {
File storeDirectory;
if (repository != null) {
LOG.debug("create store with type: {}, name: {} and repository: {}", type, name, repository.getNamespaceAndName());
storeDirectory = this.getStoreDirectory(store, repository);
@@ -84,8 +82,7 @@ public abstract class FileBasedStoreFactory {
storeDirectory = this.getStoreDirectory(store);
}
IOUtil.mkdirs(storeDirectory);
}
return new File(this.storeDirectory, name);
return new File(storeDirectory, name);
}
/**

View File

@@ -12,6 +12,6 @@
"@scm-manager/ui-extensions": "^0.1.1"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.22"
"@scm-manager/ui-bundler": "^0.0.24"
}
}

View File

@@ -707,9 +707,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.22":
version "0.0.22"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
"@scm-manager/ui-bundler@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -9,6 +9,6 @@
"@scm-manager/ui-extensions": "^0.1.1"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.22"
"@scm-manager/ui-bundler": "^0.0.24"
}
}

View File

@@ -641,9 +641,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.22":
version "0.0.22"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
"@scm-manager/ui-bundler@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -9,6 +9,6 @@
"@scm-manager/ui-extensions": "^0.1.1"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.22"
"@scm-manager/ui-bundler": "^0.0.24"
}
}

View File

@@ -641,9 +641,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.22":
version "0.0.22"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
"@scm-manager/ui-bundler@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -42,8 +42,20 @@ package sonia.scm.store;
*/
public class InMemoryConfigurationStoreFactory implements ConfigurationStoreFactory {
private ConfigurationStore store;
public InMemoryConfigurationStoreFactory() {
}
public InMemoryConfigurationStoreFactory(ConfigurationStore store) {
this.store = store;
}
@Override
public ConfigurationStore getStore(TypedStoreParameters storeParameters) {
if (store != null) {
return store;
}
return new InMemoryConfigurationStore<>();
}
}

View File

@@ -0,0 +1,53 @@
package sonia.scm.store;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.UUIDKeyGenerator;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* In memory store implementation of {@link DataStore}.
*
* @author Sebastian Sdorra
*
* @param <T> type of stored object
*/
public class InMemoryDataStore<T> implements DataStore<T> {
private final Map<String, T> store = new HashMap<>();
private KeyGenerator generator = new UUIDKeyGenerator();
@Override
public String put(T item) {
String key = generator.createKey();
store.put(key, item);
return key;
}
@Override
public void put(String id, T item) {
store.put(id, item);
}
@Override
public Map<String, T> getAll() {
return Collections.unmodifiableMap(store);
}
@Override
public void clear() {
store.clear();
}
@Override
public void remove(String id) {
store.remove(id);
}
@Override
public T get(String id) {
return store.get(id);
}
}

View File

@@ -0,0 +1,26 @@
package sonia.scm.store;
/**
* In memory configuration store factory for testing purposes.
*
* @author Sebastian Sdorra
*/
public class InMemoryDataStoreFactory implements DataStoreFactory {
private InMemoryDataStore store;
public InMemoryDataStoreFactory() {
}
public InMemoryDataStoreFactory(InMemoryDataStore store) {
this.store = store;
}
@Override
public <T> DataStore<T> getStore(TypedStoreParameters<T> storeParameters) {
if (store != null) {
return store;
}
return new InMemoryDataStore<>();
}
}

View File

@@ -14,7 +14,7 @@
"eslint-fix": "eslint src --fix"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.22",
"@scm-manager/ui-bundler": "^0.0.24",
"create-index": "^2.3.0",
"enzyme": "^3.5.0",
"enzyme-adapter-react-16": "^1.3.1",
@@ -35,7 +35,8 @@
"react-i18next": "^7.11.0",
"react-jss": "^8.6.1",
"react-router-dom": "^4.3.1",
"react-select": "^2.1.2"
"react-select": "^2.1.2",
"diff2html": "^2.5.0"
},
"browserify": {
"transform": [

View File

@@ -63,8 +63,9 @@ class ConfigurationBinder {
// route for global configuration, passes the current repository to component
const RepoRoute = ({ url, repository }) => {
return this.route(url + to, <RepositoryComponent repository={repository}/>);
const RepoRoute = ({url, repository}) => {
const link = repository._links[linkName].href
return this.route(url + to, <RepositoryComponent repository={repository} link={link}/>);
};
// bind config route to extension point

View File

@@ -0,0 +1,36 @@
//@flow
import React from "react";
import { Diff2Html } from "diff2html";
type Props = {
diff: string,
sideBySide: boolean
};
class Diff extends React.Component<Props> {
static defaultProps = {
sideBySide: false
};
render() {
const { diff, sideBySide } = this.props;
const options = {
inputFormat: "diff",
outputFormat: sideBySide ? "side-by-side" : "line-by-line",
showFiles: false,
matching: "lines"
};
const outputHtml = Diff2Html.getPrettyHtml(diff, options);
return (
// eslint-disable-next-line react/no-danger
<div dangerouslySetInnerHTML={{ __html: outputHtml }} />
);
}
}
export default Diff;

View File

@@ -0,0 +1,64 @@
//@flow
import React from "react";
import { apiClient } from "../apiclient";
import ErrorNotification from "../ErrorNotification";
import Loading from "../Loading";
import Diff from "./Diff";
type Props = {
url: string,
sideBySide: boolean
};
type State = {
diff?: string,
loading: boolean,
error?: Error
};
class LoadingDiff extends React.Component<Props, State> {
static defaultProps = {
sideBySide: false
};
constructor(props: Props) {
super(props);
this.state = {
loading: true
};
}
componentDidMount() {
const { url } = this.props;
apiClient
.get(url)
.then(response => response.text())
.then(text => {
this.setState({
loading: false,
diff: text
});
})
.catch(error => {
this.setState({
loading: false,
error
});
});
}
render() {
const { diff, loading, error } = this.state;
if (error) {
return <ErrorNotification error={error} />;
} else if (loading || !diff) {
return <Loading />;
} else {
return <Diff diff={diff} />;
}
}
}
export default LoadingDiff;

View File

@@ -0,0 +1,37 @@
//@flow
import React from "react";
import type { Changeset } from "@scm-manager/ui-types";
import LoadingDiff from "../LoadingDiff";
import Notification from "../../Notification";
import {translate} from "react-i18next";
type Props = {
changeset: Changeset,
// context props
t: string => string
};
class ChangesetDiff extends React.Component<Props> {
isDiffSupported(changeset: Changeset) {
return !!changeset._links.diff;
}
createUrl(changeset: Changeset) {
return changeset._links.diff.href + "?format=GIT";
}
render() {
const { changeset, t } = this.props;
if (!this.isDiffSupported(changeset)) {
return <Notification type="danger">{t("changesets.diff.not-supported")}</Notification>;
} else {
const url = this.createUrl(changeset);
return <LoadingDiff url={url} />;
}
}
}
export default translate("repos")(ChangesetDiff);

View File

@@ -7,3 +7,4 @@ export { default as ChangesetId } from "./ChangesetId";
export { default as ChangesetList } from "./ChangesetList";
export { default as ChangesetRow } from "./ChangesetRow";
export { default as ChangesetTag } from "./ChangesetTag";
export { default as ChangesetDiff } from "./ChangesetDiff";

View File

@@ -1,3 +1,5 @@
// @flow
export * from "./changesets";
export { default as Diff } from "./Diff";
export { default as LoadingDiff } from "./LoadingDiff";

View File

@@ -687,9 +687,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.22":
version "0.0.22"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
"@scm-manager/ui-bundler@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"
@@ -2444,7 +2444,16 @@ dev-ip@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0"
diff@^3.2.0:
diff2html@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-2.5.0.tgz#2d16f1a8f115354733b16b0264a594fa7db98aa2"
dependencies:
diff "^3.5.0"
hogan.js "^3.0.2"
lodash "^4.17.11"
whatwg-fetch "^3.0.0"
diff@^3.2.0, diff@^3.5.0:
version "3.5.0"
resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12"
@@ -3858,6 +3867,13 @@ hoek@2.x.x:
version "2.16.3"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed"
hogan.js@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/hogan.js/-/hogan.js-3.0.2.tgz#4cd9e1abd4294146e7679e41d7898732b02c7bfd"
dependencies:
mkdirp "0.3.0"
nopt "1.0.10"
hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0:
version "2.5.5"
resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47"
@@ -5258,7 +5274,7 @@ lodash.templatesettings@^3.0.0:
lodash._reinterpolate "^3.0.0"
lodash.escape "^3.0.0"
lodash@^4.13.1, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5:
lodash@^4.13.1, lodash@^4.15.0, lodash@^4.16.6, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
@@ -5518,6 +5534,10 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
mkdirp@0.3.0:
version "0.3.0"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -5696,6 +5716,12 @@ nomnom@~1.6.2:
colors "0.5.x"
underscore "~1.4.4"
nopt@1.0.10, nopt@~1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
dependencies:
abbrev "1"
nopt@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d"
@@ -5703,12 +5729,6 @@ nopt@^4.0.1:
abbrev "1"
osenv "^0.1.4"
nopt@~1.0.10:
version "1.0.10"
resolved "https://registry.yarnpkg.com/nopt/-/nopt-1.0.10.tgz#6ddd21bd2a31417b92727dd585f8a6f37608ebee"
dependencies:
abbrev "1"
normalize-package-data@^2.3.2:
version "2.4.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f"
@@ -8040,6 +8060,10 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"
whatwg-mimetype@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz#a3d58ef10b76009b042d03e25591ece89b88d171"

View File

@@ -14,7 +14,7 @@
"check": "flow check"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.22"
"@scm-manager/ui-bundler": "^0.0.24"
},
"browserify": {
"transform": [

View File

@@ -707,9 +707,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.22":
version "0.0.22"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
"@scm-manager/ui-bundler@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"

View File

@@ -11,7 +11,7 @@
"bulma": "^0.7.1",
"bulma-tooltip": "^2.0.2",
"classnames": "^2.2.5",
"diff2html": "^2.4.0",
"diff2html": "^2.5.0",
"font-awesome": "^4.7.0",
"history": "^4.7.2",
"i18next": "^11.4.0",
@@ -21,7 +21,6 @@
"node-sass": "^4.9.3",
"postcss-easy-import": "^3.0.0",
"react": "^16.4.2",
"react-diff-view": "^1.7.0",
"react-dom": "^16.4.2",
"react-i18next": "^7.9.0",
"react-jss": "^8.6.0",
@@ -52,7 +51,7 @@
"pre-commit": "jest && flow && eslint src"
},
"devDependencies": {
"@scm-manager/ui-bundler": "^0.0.22",
"@scm-manager/ui-bundler": "^0.0.24",
"concat": "^1.0.3",
"copyfiles": "^2.0.0",
"enzyme": "^3.3.0",

View File

@@ -66,6 +66,9 @@
}
},
"changesets": {
"diff": {
"not-supported": "Diff of changesets is not supported by the type of repository"
},
"error-title": "Error",
"error-subtitle": "Could not fetch changesets",
"changeset": {

View File

@@ -9,14 +9,14 @@ import {
ChangesetId,
ChangesetTag,
ChangesetAuthor,
ChangesetDiff,
AvatarWrapper,
AvatarImage,
changesets
changesets,
} from "@scm-manager/ui-components";
import classNames from "classnames";
import type { Tag } from "@scm-manager/ui-types";
import ScmDiff from "../../containers/ScmDiff";
const styles = {
spacing: {
@@ -78,7 +78,7 @@ class ChangesetDetails extends React.Component<Props> {
</p>
</div>
<div>
<ScmDiff changeset={changeset} sideBySide={false}/>
<ChangesetDiff changeset={changeset} />
</div>
</div>
);

View File

@@ -11,6 +11,9 @@ import classNames from "classnames";
const styles = {
zeroflex: {
flexGrow: 0
},
minWidthOfLabel: {
minWidth: "4.5rem"
}
};
@@ -45,7 +48,12 @@ class BranchSelector extends React.Component<Props, State> {
return (
<div className="box field is-horizontal">
<div
className={classNames("field-label", "is-normal", classes.zeroflex)}
className={classNames(
"field-label",
"is-normal",
classes.zeroflex,
classes.minWidthOfLabel
)}
>
<label className="label">{t("branch-selector.label")}</label>
</div>

View File

@@ -114,7 +114,7 @@ class RepositoryRoot extends React.Component<Props> {
return (
<Page title={repository.namespace + "/" + repository.name}>
<div className="columns">
<div className="column is-three-quarters">
<div className="column is-three-quarters is-clipped">
<Switch>
<Route
path={url}

View File

@@ -1,51 +0,0 @@
// @flow
import React from "react";
import { apiClient } from "@scm-manager/ui-components";
import type { Changeset } from "@scm-manager/ui-types";
import { Diff2Html } from "diff2html";
type Props = {
changeset: Changeset,
sideBySide: boolean
};
type State = {
diff: string,
error?: Error
};
class ScmDiff extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { diff: "" };
}
componentDidMount() {
const { changeset } = this.props;
const url = changeset._links.diff.href+"?format=GIT";
apiClient
.get(url)
.then(response => response.text())
.then(text => this.setState({ ...this.state, diff: text }))
.catch(error => this.setState({ ...this.state, error }));
}
render() {
const options = {
inputFormat: "diff",
outputFormat: this.props.sideBySide ? "side-by-side" : "line-by-line",
showFiles: false,
matching: "lines"
};
const outputHtml = Diff2Html.getPrettyHtml(this.state.diff, options);
return (
// eslint-disable-next-line react/no-danger
<div dangerouslySetInnerHTML={{ __html: outputHtml }} />
);
}
}
export default ScmDiff;

View File

@@ -119,7 +119,9 @@ class FileTree extends React.Component<Props> {
<th className="is-hidden-mobile">
{t("sources.file-tree.lastModified")}
</th>
<th>{t("sources.file-tree.description")}</th>
<th className="is-hidden-mobile">
{t("sources.file-tree.description")}
</th>
</tr>
</thead>
<tbody>

View File

@@ -6,10 +6,14 @@ import FileSize from "./FileSize";
import FileIcon from "./FileIcon";
import { Link } from "react-router-dom";
import type { File } from "@scm-manager/ui-types";
import classNames from "classnames";
const styles = {
iconColumn: {
width: "16px"
},
wordBreakMinWidth: {
minWidth: "10em"
}
};
@@ -71,12 +75,14 @@ class FileTreeLeaf extends React.Component<Props> {
return (
<tr>
<td className={classes.iconColumn}>{this.createFileIcon(file)}</td>
<td>{this.createFileName(file)}</td>
<td className={classNames(classes.wordBreakMinWidth, "is-word-break")}>{this.createFileName(file)}</td>
<td className="is-hidden-mobile">{fileSize}</td>
<td className="is-hidden-mobile">
<DateFromNow date={file.lastModified} />
</td>
<td>{file.description}</td>
<td className={classNames(classes.wordBreakMinWidth, "is-word-break", "is-hidden-mobile")}>
{file.description}
</td>
</tr>
);
}

View File

@@ -41,6 +41,13 @@ const styles = {
isVerticalCenter: {
display: "flex",
alignItems: "center"
},
wordBreak: {
WebkitHyphens: "auto",
MozHyphens: "auto",
MsHyphens: "auto",
hypens: "auto",
wordBreak: "break-all",
}
};
@@ -93,7 +100,7 @@ class Content extends React.Component<Props, State> {
classes.marginInHeader
)}
/>
<span>{file.name}</span>
<span className={classes.wordBreak}>{file.name}</span>
</div>
<div className="media-right">{selector}</div>
</article>
@@ -125,11 +132,11 @@ class Content extends React.Component<Props, State> {
<tbody>
<tr>
<td>{t("sources.content.path")}</td>
<td>{file.path}</td>
<td className={classes.wordBreak}>{file.path}</td>
</tr>
<tr>
<td>{t("sources.content.branch")}</td>
<td>{revision}</td>
<td className={classes.wordBreak}>{revision}</td>
</tr>
<tr>
<td>{t("sources.content.size")}</td>
@@ -141,7 +148,7 @@ class Content extends React.Component<Props, State> {
</tr>
<tr>
<td>{t("sources.content.description")}</td>
<td>{description}</td>
<td className={classes.wordBreak}>{description}</td>
</tr>
</tbody>
</table>

View File

@@ -27,6 +27,14 @@ $blue: #33B2E8;
padding: 0 0 0 3.8em !important;
}
.is-word-break {
-webkit-hyphens: auto;
-moz-hyphens: auto;
-ms-hyphens: auto;
hyphens: auto;
word-break: break-all;
}
.main {
min-height: calc(100vh - 260px);
}

View File

@@ -698,9 +698,9 @@
version "0.0.2"
resolved "https://registry.yarnpkg.com/@scm-manager/eslint-config/-/eslint-config-0.0.2.tgz#94cc8c3fb4f51f870b235893dc134fc6c423ae85"
"@scm-manager/ui-bundler@^0.0.22":
version "0.0.22"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.22.tgz#6eaed4e1f0b1fbc6ed1ebbf7eb0f5585f760949a"
"@scm-manager/ui-bundler@^0.0.24":
version "0.0.24"
resolved "https://registry.yarnpkg.com/@scm-manager/ui-bundler/-/ui-bundler-0.0.24.tgz#034d5500c79b438c48d8f7ee985be07c4ea46d1e"
dependencies:
"@babel/core" "^7.0.0"
"@babel/plugin-proposal-class-properties" "^7.0.0"
@@ -1925,7 +1925,7 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
classnames@^2.2.5, classnames@^2.2.6:
classnames@^2.2.5:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
@@ -2549,14 +2549,14 @@ dev-ip@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/dev-ip/-/dev-ip-1.0.1.tgz#a76a3ed1855be7a012bb8ac16cb80f3c00dc28f0"
diff2html@^2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-2.4.0.tgz#de632384eefa5a7f6b0e92eafb1fa25d22dc88ab"
diff2html@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/diff2html/-/diff2html-2.5.0.tgz#2d16f1a8f115354733b16b0264a594fa7db98aa2"
dependencies:
diff "^3.5.0"
hogan.js "^3.0.2"
lodash "^4.17.10"
whatwg-fetch "^2.0.4"
lodash "^4.17.11"
whatwg-fetch "^3.0.0"
diff@^3.2.0, diff@^3.5.0:
version "3.5.0"
@@ -3604,10 +3604,6 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
gitdiff-parser@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/gitdiff-parser/-/gitdiff-parser-0.1.2.tgz#26a256e05e9c2d5016b512a96c1dacb40862b92a"
glob-base@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4"
@@ -5481,10 +5477,6 @@ lodash.escape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
lodash.findlastindex@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.findlastindex/-/lodash.findlastindex-4.6.0.tgz#b8375ac0f02e9b926375cdf8dc3ea814abf9c6ac"
lodash.flattendeep@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2"
@@ -5517,10 +5509,6 @@ lodash.keys@^3.0.0:
lodash.isarguments "^3.0.0"
lodash.isarray "^3.0.0"
lodash.mapvalues@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c"
lodash.memoize@~3.0.3:
version "3.0.4"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
@@ -5558,7 +5546,7 @@ lodash.templatesettings@^3.0.0:
lodash._reinterpolate "^3.0.0"
lodash.escape "^3.0.0"
lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10:
version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
@@ -5861,7 +5849,7 @@ mixin-deep@^1.2.0:
mkdirp@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.3.0.tgz#1bbf5ab1ba827af23575143490426455f481fe1e"
"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1:
version "0.5.1"
@@ -6919,18 +6907,6 @@ rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-diff-view@^1.7.0:
version "1.8.1"
resolved "https://registry.yarnpkg.com/react-diff-view/-/react-diff-view-1.8.1.tgz#0b9b4adcb92de6730d28177d68654dfcc2097f73"
dependencies:
classnames "^2.2.6"
gitdiff-parser "^0.1.2"
leven "^2.1.0"
lodash.escape "^4.0.1"
lodash.findlastindex "^4.6.0"
lodash.mapvalues "^4.6.0"
warning "^4.0.1"
react-dom@^16.4.2:
version "16.5.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.5.2.tgz#b69ee47aa20bab5327b2b9d7c1fe2a30f2cfa9d7"
@@ -8730,10 +8706,6 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3:
dependencies:
iconv-lite "0.4.24"
whatwg-fetch@^2.0.4:
version "2.0.4"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f"
whatwg-fetch@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb"

View File

@@ -79,14 +79,14 @@ import sonia.scm.repository.spi.HookEventFacade;
import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.schedule.QuartzScheduler;
import sonia.scm.schedule.Scheduler;
import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.security.AuthorizationChangedEventProducer;
import sonia.scm.security.CipherHandler;
import sonia.scm.security.CipherUtil;
import sonia.scm.security.ConfigurableLoginAttemptHandler;
import sonia.scm.security.DefaultJwtAccessTokenRefreshStrategy;
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.DefaultSecuritySystem;
import sonia.scm.security.JwtAccessTokenRefreshStrategy;
import sonia.scm.security.KeyGenerator;
import sonia.scm.security.LoginAttemptHandler;
import sonia.scm.security.SecuritySystem;
@@ -320,6 +320,7 @@ public class ScmServletModule extends ServletModule
// bind events
// bind(LastModifiedUpdateListener.class);
bind(AccessTokenCookieIssuer.class).to(DefaultAccessTokenCookieIssuer.class);
bind(PushStateDispatcher.class).toProvider(PushStateDispatcherProvider.class);
}

View File

@@ -0,0 +1,16 @@
package sonia.scm.api.rest;
import sonia.scm.BadRequestException;
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
@Provider
public class BadRequestExceptionMapper extends ContextualExceptionMapper<BadRequestException> {
@Inject
public BadRequestExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
super(BadRequestException.class, Response.Status.BAD_REQUEST, mapper);
}
}

View File

@@ -46,7 +46,7 @@ import org.apache.shiro.SecurityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotFoundException;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.FeatureNotSupportedException;
import sonia.scm.Type;
import sonia.scm.api.rest.RestActionUploadResult;
import sonia.scm.api.v2.resources.RepositoryResource;
@@ -394,7 +394,7 @@ public class RepositoryImportResource
response = Response.ok(result).build();
}
catch (NotSupportedFeatureException ex)
catch (FeatureNotSupportedException ex)
{
logger
.warn(
@@ -609,7 +609,7 @@ public class RepositoryImportResource
types.add(t);
}
}
catch (NotSupportedFeatureException ex)
catch (FeatureNotSupportedException ex)
{
if (logger.isTraceEnabled())
{
@@ -711,7 +711,7 @@ public class RepositoryImportResource
}
}
}
catch (NotSupportedFeatureException ex)
catch (FeatureNotSupportedException ex)
{
throw new WebApplicationException(ex, Response.Status.BAD_REQUEST);
}

View File

@@ -1,17 +0,0 @@
package sonia.scm.api.v2;
import sonia.scm.NotSupportedFeatureException;
import sonia.scm.api.rest.ContextualExceptionMapper;
import sonia.scm.api.v2.resources.ExceptionWithContextToErrorDtoMapper;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
@Provider
public class NotSupportedFeatureExceptionMapper extends ContextualExceptionMapper<NotSupportedFeatureException> {
@Inject
public NotSupportedFeatureExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
super(NotSupportedFeatureException.class, Response.Status.BAD_REQUEST, mapper);
}
}

View File

@@ -1,17 +0,0 @@
package sonia.scm.api.v2.resources;
import sonia.scm.api.rest.ContextualExceptionMapper;
import sonia.scm.user.ChangePasswordNotAllowedException;
import sonia.scm.user.InvalidPasswordException;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
@Provider
public class ChangePasswordNotAllowedExceptionMapper extends ContextualExceptionMapper<ChangePasswordNotAllowedException> {
@Inject
public ChangePasswordNotAllowedExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
super(ChangePasswordNotAllowedException.class, Response.Status.BAD_REQUEST, mapper);
}
}

View File

@@ -1,17 +0,0 @@
package sonia.scm.api.v2.resources;
import sonia.scm.api.rest.ContextualExceptionMapper;
import sonia.scm.user.InvalidPasswordException;
import javax.inject.Inject;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;
@Provider
public class InvalidPasswordExceptionMapper extends ContextualExceptionMapper<InvalidPasswordException> {
@Inject
public InvalidPasswordExceptionMapper(ExceptionWithContextToErrorDtoMapper mapper) {
super(InvalidPasswordException.class, Response.Status.BAD_REQUEST, mapper);
}
}

View File

@@ -51,12 +51,12 @@ import java.util.concurrent.TimeUnit;
* @author Sebastian Sdorra
* @since 2.0.0
*/
public final class AccessTokenCookieIssuer {
public final class DefaultAccessTokenCookieIssuer implements AccessTokenCookieIssuer {
/**
* the logger for AccessTokenCookieIssuer
* the logger for DefaultAccessTokenCookieIssuer
*/
private static final Logger LOG = LoggerFactory.getLogger(AccessTokenCookieIssuer.class);
private static final Logger LOG = LoggerFactory.getLogger(DefaultAccessTokenCookieIssuer.class);
private final ScmConfiguration configuration;
@@ -66,7 +66,7 @@ public final class AccessTokenCookieIssuer {
* @param configuration scm main configuration
*/
@Inject
public AccessTokenCookieIssuer(ScmConfiguration configuration) {
public DefaultAccessTokenCookieIssuer(ScmConfiguration configuration) {
this.configuration = configuration;
}

View File

@@ -87,6 +87,7 @@ public final class JwtAccessToken implements AccessToken {
return ofNullable(claims.get(REFRESHABLE_UNTIL_CLAIM_KEY, Date.class));
}
@Override
public Optional<String> getParentKey() {
return ofNullable(claims.get(PARENT_TOKEN_ID_CLAIM_KEY).toString());
}

View File

@@ -18,6 +18,7 @@ import sonia.scm.security.AccessToken;
import sonia.scm.security.AccessTokenBuilder;
import sonia.scm.security.AccessTokenBuilderFactory;
import sonia.scm.security.AccessTokenCookieIssuer;
import sonia.scm.security.DefaultAccessTokenCookieIssuer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -46,7 +47,7 @@ public class AuthenticationResourceTest {
@Mock
private AccessTokenBuilder accessTokenBuilder;
private AccessTokenCookieIssuer cookieIssuer = new AccessTokenCookieIssuer(mock(ScmConfiguration.class));
private AccessTokenCookieIssuer cookieIssuer = new DefaultAccessTokenCookieIssuer(mock(ScmConfiguration.class));
private static final String AUTH_JSON_TRILLIAN = "{\n" +
"\t\"cookie\": true,\n" +

View File

@@ -4,9 +4,9 @@ import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.mock.MockDispatcherFactory;
import sonia.scm.api.rest.AlreadyExistsExceptionMapper;
import sonia.scm.api.rest.AuthorizationExceptionMapper;
import sonia.scm.api.rest.BadRequestExceptionMapper;
import sonia.scm.api.rest.ConcurrentModificationExceptionMapper;
import sonia.scm.api.v2.NotFoundExceptionMapper;
import sonia.scm.api.v2.NotSupportedFeatureExceptionMapper;
public class DispatcherMock {
public static Dispatcher createDispatcher(Object resource) {
@@ -18,9 +18,7 @@ public class DispatcherMock {
dispatcher.getProviderFactory().register(new ConcurrentModificationExceptionMapper(mapper));
dispatcher.getProviderFactory().registerProvider(AuthorizationExceptionMapper.class);
dispatcher.getProviderFactory().register(new InternalRepositoryExceptionMapper(mapper));
dispatcher.getProviderFactory().register(new ChangePasswordNotAllowedExceptionMapper(mapper));
dispatcher.getProviderFactory().register(new InvalidPasswordExceptionMapper(mapper));
dispatcher.getProviderFactory().register(new NotSupportedFeatureExceptionMapper(mapper));
dispatcher.getProviderFactory().register(new BadRequestExceptionMapper(mapper));
return dispatcher;
}
}

View File

@@ -20,11 +20,11 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class AccessTokenCookieIssuerTest {
public class DefaultAccessTokenCookieIssuerTest {
private ScmConfiguration configuration;
private AccessTokenCookieIssuer issuer;
private DefaultAccessTokenCookieIssuer issuer;
@Mock
private HttpServletRequest request;
@@ -41,7 +41,7 @@ public class AccessTokenCookieIssuerTest {
@Before
public void setUp() {
configuration = new ScmConfiguration();
issuer = new AccessTokenCookieIssuer(configuration);
issuer = new DefaultAccessTokenCookieIssuer(configuration);
}
@Test