mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
Merged in feature/namespace_strategies (pull request #214)
Feature/namespace strategies
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user