Merged in feature/namespace_strategies (pull request #214)

Feature/namespace strategies
This commit is contained in:
René Pfeuffer
2019-03-13 11:20:36 +00:00
55 changed files with 1229 additions and 208 deletions

View File

@@ -35,7 +35,7 @@ public class ConfigDto extends HalRepresentation {
private String pluginUrl;
private long loginAttemptLimitTimeout;
private boolean enabledXsrfProtection;
private String defaultNamespaceStrategy;
private String namespaceStrategy;
@Override
@SuppressWarnings("squid:S1185") // We want to have this method available in this package

View File

@@ -5,6 +5,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
import com.webcohesion.enunciate.metadata.rs.TypeHint;
import sonia.scm.config.ConfigurationPermissions;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.NamespaceStrategyValidator;
import sonia.scm.util.ScmConfigurationUtil;
import sonia.scm.web.VndMediaType;
@@ -27,12 +28,16 @@ public class ConfigResource {
private final ConfigDtoToScmConfigurationMapper dtoToConfigMapper;
private final ScmConfigurationToConfigDtoMapper configToDtoMapper;
private final ScmConfiguration configuration;
private final NamespaceStrategyValidator namespaceStrategyValidator;
@Inject
public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper, ScmConfigurationToConfigDtoMapper configToDtoMapper, ScmConfiguration configuration) {
public ConfigResource(ConfigDtoToScmConfigurationMapper dtoToConfigMapper,
ScmConfigurationToConfigDtoMapper configToDtoMapper,
ScmConfiguration configuration, NamespaceStrategyValidator namespaceStrategyValidator) {
this.dtoToConfigMapper = dtoToConfigMapper;
this.configToDtoMapper = configToDtoMapper;
this.configuration = configuration;
this.namespaceStrategyValidator = namespaceStrategyValidator;
}
/**
@@ -78,6 +83,9 @@ public class ConfigResource {
// But to where to check? load() or store()? Leave it for now, SCMv1 legacy that can be cleaned up later.
ConfigurationPermissions.write(configuration).check();
// ensure the namespace strategy is valid
namespaceStrategyValidator.check(configDto.getNamespaceStrategy());
ScmConfiguration config = dtoToConfigMapper.map(configDto);
synchronized (ScmConfiguration.class) {
configuration.load(config);

View File

@@ -59,6 +59,9 @@ public class IndexDtoGenerator extends HalAppenderMapper {
builder.single(link("permissions", resourceLinks.permissions().self()));
}
builder.single(link("availableRepositoryPermissions", resourceLinks.availableRepositoryPermissions().self()));
builder.single(link("repositoryTypes", resourceLinks.repositoryTypeCollection().self()));
builder.single(link("namespaceStrategies", resourceLinks.namespaceStrategies().self()));
} else {
builder.single(link("login", resourceLinks.authentication().jsonLogin()));
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
import lombok.Getter;
import java.util.List;
@Getter
public class NamespaceStrategiesDto extends HalRepresentation {
private String current;
private List<String> available;
public NamespaceStrategiesDto(String current, List<String> available, Links links) {
super(links);
this.current = current;
this.available = available;
}
}

View File

@@ -0,0 +1,63 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Links;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.web.VndMediaType;
import javax.inject.Inject;
import javax.inject.Provider;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* RESTFul WebService Endpoint for namespace strategies.
*/
@Path(NamespaceStrategyResource.PATH)
public class NamespaceStrategyResource {
static final String PATH = "v2/namespaceStrategies";
private Set<NamespaceStrategy> namespaceStrategies;
private Provider<NamespaceStrategy> namespaceStrategyProvider;
@Inject
public NamespaceStrategyResource(Set<NamespaceStrategy> namespaceStrategies, Provider<NamespaceStrategy> namespaceStrategyProvider) {
this.namespaceStrategies = namespaceStrategies;
this.namespaceStrategyProvider = namespaceStrategyProvider;
}
/**
* Returns all available namespace strategies and the current selected.
*
* @param uriInfo uri info
*
* @return available and current namespace strategies
*/
@GET
@Path("")
@Produces(VndMediaType.NAMESPACE_STRATEGIES)
public NamespaceStrategiesDto get(@Context UriInfo uriInfo) {
String currentStrategy = strategyAsString(namespaceStrategyProvider.get());
List<String> availableStrategies = collectStrategyNames();
return new NamespaceStrategiesDto(currentStrategy, availableStrategies, createLinks(uriInfo));
}
private Links createLinks(@Context UriInfo uriInfo) {
return Links.linkingTo().self(uriInfo.getAbsolutePath().toASCIIString()).build();
}
private String strategyAsString(NamespaceStrategy namespaceStrategy) {
return namespaceStrategy.getClass().getSimpleName();
}
private List<String> collectStrategyNames() {
return namespaceStrategies.stream().map(this::strategyAsString).collect(Collectors.toList());
}
}

View File

@@ -9,6 +9,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.validator.constraints.Email;
import org.hibernate.validator.constraints.NotEmpty;
import sonia.scm.util.ValidationUtil;
import javax.validation.constraints.Pattern;
import java.time.Instant;
@@ -25,8 +26,9 @@ public class RepositoryDto extends HalRepresentation {
private List<HealthCheckFailureDto> healthCheckFailures;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant lastModified;
// we could not validate the namespace, this must be done by the namespace strategy
private String namespace;
@Pattern(regexp = "^[A-z0-9\\-_]+$")
@Pattern(regexp = ValidationUtil.REGEX_REPOSITORYNAME)
private String name;
private boolean archived = false;
@NotEmpty

View File

@@ -277,6 +277,23 @@ class ResourceLinks {
}
}
public NamespaceStrategiesLinks namespaceStrategies() {
return new NamespaceStrategiesLinks(scmPathInfoStore.get());
}
static class NamespaceStrategiesLinks {
private final LinkBuilder namespaceStrategiesLinkBuilder;
NamespaceStrategiesLinks(ScmPathInfo pathInfo) {
namespaceStrategiesLinkBuilder = new LinkBuilder(pathInfo, NamespaceStrategyResource.class);
}
String self() {
return namespaceStrategiesLinkBuilder.method("get").parameters().href();
}
}
public RepositoryTypeLinks repositoryType() {
return new RepositoryTypeLinks(scmPathInfoStore.get());
}

View File

@@ -0,0 +1,29 @@
package sonia.scm.repository;
import com.google.common.annotations.VisibleForTesting;
import sonia.scm.plugin.Extension;
import javax.inject.Inject;
import java.time.Clock;
import java.time.Year;
@Extension
public class CurrentYearNamespaceStrategy implements NamespaceStrategy {
private final Clock clock;
@Inject
public CurrentYearNamespaceStrategy() {
this(Clock.systemDefaultZone());
}
@VisibleForTesting
CurrentYearNamespaceStrategy(Clock clock) {
this.clock = clock;
}
@Override
public String createNamespace(Repository repository) {
return String.valueOf(Year.now(clock).getValue());
}
}

View File

@@ -0,0 +1,20 @@
package sonia.scm.repository;
import sonia.scm.plugin.Extension;
import sonia.scm.util.ValidationUtil;
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow;
@Extension
public class CustomNamespaceStrategy implements NamespaceStrategy {
@Override
public String createNamespace(Repository repository) {
String namespace = repository.getNamespace();
doThrow()
.violation("invalid namespace", "namespace")
.when(!ValidationUtil.isRepositoryNameValid(namespace));
return namespace;
}
}

View File

@@ -1,24 +0,0 @@
package sonia.scm.repository;
import com.google.common.base.Strings;
import org.apache.shiro.SecurityUtils;
import sonia.scm.plugin.Extension;
/**
* The DefaultNamespaceStrategy returns the predefined namespace of the given repository, if the namespace was not set
* the username of the currently loggedin user is used.
*
* @since 2.0.0
*/
@Extension
public class DefaultNamespaceStrategy implements NamespaceStrategy {
@Override
public String createNamespace(Repository repository) {
String namespace = repository.getNamespace();
if (Strings.isNullOrEmpty(namespace)) {
namespace = SecurityUtils.getSubject().getPrincipal().toString();
}
return namespace;
}
}

View File

@@ -52,6 +52,7 @@ import sonia.scm.util.CollectionAppender;
import sonia.scm.util.IOUtil;
import sonia.scm.util.Util;
import javax.inject.Provider;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -85,7 +86,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final KeyGenerator keyGenerator;
private final RepositoryDAO repositoryDAO;
private final Set<Type> types;
private NamespaceStrategy namespaceStrategy;
private final Provider<NamespaceStrategy> namespaceStrategyProvider;
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
@@ -93,11 +94,11 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
public DefaultRepositoryManager(ScmConfiguration configuration,
SCMContextProvider contextProvider, KeyGenerator keyGenerator,
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
NamespaceStrategy namespaceStrategy) {
Provider<NamespaceStrategy> namespaceStrategyProvider) {
this.configuration = configuration;
this.keyGenerator = keyGenerator;
this.repositoryDAO = repositoryDAO;
this.namespaceStrategy = namespaceStrategy;
this.namespaceStrategyProvider = namespaceStrategyProvider;
ThreadFactory factory = new ThreadFactoryBuilder()
.setNameFormat(THREAD_NAME).build();
@@ -131,7 +132,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
public Repository create(Repository repository, boolean initRepository) {
repository.setId(keyGenerator.createKey());
repository.setNamespace(namespaceStrategy.createNamespace(repository));
repository.setNamespace(namespaceStrategyProvider.get().createNamespace(repository));
logger.info("create repository {}/{} of type {} in namespace {}", repository.getNamespace(), repository.getName(), repository.getType(), repository.getNamespace());

View File

@@ -0,0 +1,38 @@
package sonia.scm.repository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.config.ScmConfiguration;
import javax.inject.Inject;
import javax.inject.Provider;
import java.util.Set;
public class NamespaceStrategyProvider implements Provider<NamespaceStrategy> {
private static final Logger LOG = LoggerFactory.getLogger(NamespaceStrategyProvider.class);
private final Set<NamespaceStrategy> strategies;
private final ScmConfiguration scmConfiguration;
@Inject
public NamespaceStrategyProvider(Set<NamespaceStrategy> strategies, ScmConfiguration scmConfiguration) {
this.strategies = strategies;
this.scmConfiguration = scmConfiguration;
}
@Override
public NamespaceStrategy get() {
String namespaceStrategy = scmConfiguration.getNamespaceStrategy();
for (NamespaceStrategy s : this.strategies) {
if (s.getClass().getSimpleName().equals(namespaceStrategy)) {
return s;
}
}
LOG.warn("could not find namespace strategy {}, using default strategy", namespaceStrategy);
return new UsernameNamespaceStrategy();
}
}

View File

@@ -0,0 +1,26 @@
package sonia.scm.repository;
import javax.inject.Inject;
import java.util.Set;
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow;
public class NamespaceStrategyValidator {
private final Set<NamespaceStrategy> strategies;
@Inject
public NamespaceStrategyValidator(Set<NamespaceStrategy> strategies) {
this.strategies = strategies;
}
public void check(String name) {
doThrow()
.violation("unknown NamespaceStrategy " + name, "namespaceStrategy")
.when(!isValid(name));
}
private boolean isValid(String name) {
return strategies.stream().anyMatch(ns -> ns.getClass().getSimpleName().equals(name));
}
}

View File

@@ -0,0 +1,11 @@
package sonia.scm.repository;
import sonia.scm.plugin.Extension;
@Extension
public class RepositoryTypeNamespaceStrategy implements NamespaceStrategy {
@Override
public String createNamespace(Repository repository) {
return repository.getType();
}
}

View File

@@ -0,0 +1,13 @@
package sonia.scm.repository;
import org.apache.shiro.SecurityUtils;
import sonia.scm.plugin.Extension;
@Extension
public class UsernameNamespaceStrategy implements NamespaceStrategy {
@Override
public String createNamespace(Repository repository) {
return SecurityUtils.getSubject().getPrincipal().toString();
}
}