mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 06:25:45 +01:00
merge with develop
This commit is contained in:
@@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
### Added
|
||||||
|
- Generation of email addresses for users, where none is configured ([#1370](https://github.com/scm-manager/scm-manager/pull/1370))
|
||||||
|
|
||||||
|
## [2.7.1] - 2020-10-14
|
||||||
### Fixed
|
### Fixed
|
||||||
- Null Pointer Exception on anonymous migration with deleted repositories ([#1371](https://github.com/scm-manager/scm-manager/pull/1371))
|
- Null Pointer Exception on anonymous migration with deleted repositories ([#1371](https://github.com/scm-manager/scm-manager/pull/1371))
|
||||||
- Null Pointer Exception on parsing SVN properties ([#1373](https://github.com/scm-manager/scm-manager/pull/1373))
|
- Null Pointer Exception on parsing SVN properties ([#1373](https://github.com/scm-manager/scm-manager/pull/1373))
|
||||||
|
|||||||
@@ -11,6 +11,12 @@ title: Intellij IDEA Configuration
|
|||||||
|
|
||||||
### Settings
|
### Settings
|
||||||
|
|
||||||
|
* Build, Execution, Deployment / Compiler
|
||||||
|
* Add runtime assertions for non-null-annotated methods and parameters (must be checked)
|
||||||
|
* Configure annotation ... (of "Add runtime assertions...")
|
||||||
|
* Nullable annotations: select (✓) `javax.annotation.Nullable`
|
||||||
|
* NotNull annotations: select (✓) `javax.annotation.Nonnull` and check Instrument
|
||||||
|
|
||||||
* Run Configurations / Edit Configuration
|
* Run Configurations / Edit Configuration
|
||||||
* Add Maven
|
* Add Maven
|
||||||
* Name: run-backend
|
* Name: run-backend
|
||||||
|
|||||||
4
pom.xml
4
pom.xml
@@ -903,7 +903,7 @@
|
|||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<!-- test libraries -->
|
<!-- test libraries -->
|
||||||
<mockito.version>3.5.11</mockito.version>
|
<mockito.version>3.5.13</mockito.version>
|
||||||
<hamcrest.version>2.1</hamcrest.version>
|
<hamcrest.version>2.1</hamcrest.version>
|
||||||
<junit.version>5.7.0</junit.version>
|
<junit.version>5.7.0</junit.version>
|
||||||
|
|
||||||
@@ -913,7 +913,7 @@
|
|||||||
<servlet.version>3.1.0</servlet.version>
|
<servlet.version>3.1.0</servlet.version>
|
||||||
|
|
||||||
<jaxrs.version>2.1.1</jaxrs.version>
|
<jaxrs.version>2.1.1</jaxrs.version>
|
||||||
<resteasy.version>4.5.7.Final</resteasy.version>
|
<resteasy.version>4.5.8.Final</resteasy.version>
|
||||||
<jersey-client.version>1.19.4</jersey-client.version>
|
<jersey-client.version>1.19.4</jersey-client.version>
|
||||||
<jackson.version>2.11.2</jackson.version>
|
<jackson.version>2.11.2</jackson.version>
|
||||||
<guice.version>4.2.3</guice.version>
|
<guice.version>4.2.3</guice.version>
|
||||||
|
|||||||
@@ -80,6 +80,14 @@ public class ScmConfiguration implements Configuration {
|
|||||||
*/
|
*/
|
||||||
public static final String DEFAULT_LOGIN_INFO_URL = "https://login-info.scm-manager.org/api/v1/login-info";
|
public static final String DEFAULT_LOGIN_INFO_URL = "https://login-info.scm-manager.org/api/v1/login-info";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default e-mail domain name that will be used whenever we have to generate an e-mail address for a user that has no
|
||||||
|
* mail address configured.
|
||||||
|
*
|
||||||
|
* @since 2.8.0
|
||||||
|
*/
|
||||||
|
public static final String DEFAULT_MAIL_DOMAIN_NAME = "scm-manager.local";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default plugin url from version 1.0
|
* Default plugin url from version 1.0
|
||||||
*/
|
*/
|
||||||
@@ -195,6 +203,8 @@ public class ScmConfiguration implements Configuration {
|
|||||||
@XmlElement(name = "login-info-url")
|
@XmlElement(name = "login-info-url")
|
||||||
private String loginInfoUrl = DEFAULT_LOGIN_INFO_URL;
|
private String loginInfoUrl = DEFAULT_LOGIN_INFO_URL;
|
||||||
|
|
||||||
|
@XmlElement(name = "mail-domain-name")
|
||||||
|
private String mailDomainName = DEFAULT_MAIL_DOMAIN_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calls the {@link sonia.scm.ConfigChangedListener#configChanged(Object)}
|
* Calls the {@link sonia.scm.ConfigChangedListener#configChanged(Object)}
|
||||||
@@ -235,6 +245,7 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.namespaceStrategy = other.namespaceStrategy;
|
this.namespaceStrategy = other.namespaceStrategy;
|
||||||
this.loginInfoUrl = other.loginInfoUrl;
|
this.loginInfoUrl = other.loginInfoUrl;
|
||||||
this.releaseFeedUrl = other.releaseFeedUrl;
|
this.releaseFeedUrl = other.releaseFeedUrl;
|
||||||
|
this.mailDomainName = other.mailDomainName;
|
||||||
this.enabledUserConverter = other.enabledUserConverter;
|
this.enabledUserConverter = other.enabledUserConverter;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -300,6 +311,15 @@ public class ScmConfiguration implements Configuration {
|
|||||||
return releaseFeedUrl;
|
return releaseFeedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the mail domain, that will be used to create e-mail addresses for users without one whenever one is required.
|
||||||
|
* @return default mail domain
|
||||||
|
* @since 2.8.0
|
||||||
|
*/
|
||||||
|
public String getMailDomainName() {
|
||||||
|
return mailDomainName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a set of glob patterns for urls which should excluded from
|
* Returns a set of glob patterns for urls which should excluded from
|
||||||
* proxy settings.
|
* proxy settings.
|
||||||
@@ -491,6 +511,16 @@ public class ScmConfiguration implements Configuration {
|
|||||||
this.releaseFeedUrl = releaseFeedUrl;
|
this.releaseFeedUrl = releaseFeedUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mail host, that will be used to create e-mail addresses for users without one whenever one is required.
|
||||||
|
*
|
||||||
|
* @param mailDomainName The default mail domain to use
|
||||||
|
* @since 2.8.0
|
||||||
|
*/
|
||||||
|
public void setMailDomainName(String mailDomainName) {
|
||||||
|
this.mailDomainName = mailDomainName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set glob patterns for urls which are should be excluded from proxy
|
* Set glob patterns for urls which are should be excluded from proxy
|
||||||
* settings.
|
* settings.
|
||||||
|
|||||||
@@ -30,7 +30,9 @@ import sonia.scm.repository.spi.MergeCommand;
|
|||||||
import sonia.scm.repository.spi.MergeCommandRequest;
|
import sonia.scm.repository.spi.MergeCommandRequest;
|
||||||
import sonia.scm.repository.spi.MergeConflictResult;
|
import sonia.scm.repository.spi.MergeConflictResult;
|
||||||
import sonia.scm.repository.util.AuthorUtil;
|
import sonia.scm.repository.util.AuthorUtil;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -78,8 +80,12 @@ public class MergeCommandBuilder {
|
|||||||
private final MergeCommand mergeCommand;
|
private final MergeCommand mergeCommand;
|
||||||
private final MergeCommandRequest request = new MergeCommandRequest();
|
private final MergeCommandRequest request = new MergeCommandRequest();
|
||||||
|
|
||||||
MergeCommandBuilder(MergeCommand mergeCommand) {
|
@Nullable
|
||||||
|
private final EMail eMail;
|
||||||
|
|
||||||
|
MergeCommandBuilder(MergeCommand mergeCommand, @Nullable EMail eMail) {
|
||||||
this.mergeCommand = mergeCommand;
|
this.mergeCommand = mergeCommand;
|
||||||
|
this.eMail = eMail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -209,7 +215,7 @@ public class MergeCommandBuilder {
|
|||||||
* @return The result of the merge.
|
* @return The result of the merge.
|
||||||
*/
|
*/
|
||||||
public MergeCommandResult executeMerge() {
|
public MergeCommandResult executeMerge() {
|
||||||
AuthorUtil.setAuthorIfNotAvailable(request);
|
AuthorUtil.setAuthorIfNotAvailable(request, eMail);
|
||||||
Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required");
|
Preconditions.checkArgument(request.isValid(), "revision to merge and target revision is required");
|
||||||
return mergeCommand.merge(request);
|
return mergeCommand.merge(request);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ import sonia.scm.repository.spi.ModifyCommand;
|
|||||||
import sonia.scm.repository.spi.ModifyCommandRequest;
|
import sonia.scm.repository.spi.ModifyCommandRequest;
|
||||||
import sonia.scm.repository.util.AuthorUtil;
|
import sonia.scm.repository.util.AuthorUtil;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -51,7 +53,6 @@ import java.util.function.Consumer;
|
|||||||
* default a {@link sonia.scm.AlreadyExistsException} will be thrown)</li>
|
* default a {@link sonia.scm.AlreadyExistsException} will be thrown)</li>
|
||||||
* <li>modify existing files ({@link #modifyFile(String)}</li>
|
* <li>modify existing files ({@link #modifyFile(String)}</li>
|
||||||
* <li>delete existing files ({@link #deleteFile(String)}</li>
|
* <li>delete existing files ({@link #deleteFile(String)}</li>
|
||||||
* <li>move/rename existing files ({@link #moveFile(String, String)}</li>
|
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* You can collect multiple changes before they are executed with a call to {@link #execute()}.
|
* You can collect multiple changes before they are executed with a call to {@link #execute()}.
|
||||||
@@ -75,11 +76,15 @@ public class ModifyCommandBuilder {
|
|||||||
private final ModifyCommand command;
|
private final ModifyCommand command;
|
||||||
private final File workdir;
|
private final File workdir;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final EMail eMail;
|
||||||
|
|
||||||
private final ModifyCommandRequest request = new ModifyCommandRequest();
|
private final ModifyCommandRequest request = new ModifyCommandRequest();
|
||||||
|
|
||||||
ModifyCommandBuilder(ModifyCommand command, WorkdirProvider workdirProvider) {
|
ModifyCommandBuilder(ModifyCommand command, WorkdirProvider workdirProvider, @Nullable EMail eMail) {
|
||||||
this.command = command;
|
this.command = command;
|
||||||
this.workdir = workdirProvider.createNewWorkdir();
|
this.workdir = workdirProvider.createNewWorkdir();
|
||||||
|
this.eMail = eMail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,7 +129,7 @@ public class ModifyCommandBuilder {
|
|||||||
* @return The revision of the new commit.
|
* @return The revision of the new commit.
|
||||||
*/
|
*/
|
||||||
public String execute() {
|
public String execute() {
|
||||||
AuthorUtil.setAuthorIfNotAvailable(request);
|
AuthorUtil.setAuthorIfNotAvailable(request, eMail);
|
||||||
try {
|
try {
|
||||||
Preconditions.checkArgument(request.isValid(), "commit message and at least one request are required");
|
Preconditions.checkArgument(request.isValid(), "commit message and at least one request are required");
|
||||||
return command.execute(request);
|
return command.execute(request);
|
||||||
|
|||||||
@@ -34,7 +34,9 @@ import sonia.scm.repository.Repository;
|
|||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -84,30 +86,36 @@ public final class RepositoryService implements Closeable {
|
|||||||
private final PreProcessorUtil preProcessorUtil;
|
private final PreProcessorUtil preProcessorUtil;
|
||||||
private final RepositoryServiceProvider provider;
|
private final RepositoryServiceProvider provider;
|
||||||
private final Repository repository;
|
private final Repository repository;
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings({"rawtypes", "java:S3740"})
|
||||||
private final Set<ScmProtocolProvider> protocolProviders;
|
private final Set<ScmProtocolProvider> protocolProviders;
|
||||||
private final WorkdirProvider workdirProvider;
|
private final WorkdirProvider workdirProvider;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final EMail eMail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link RepositoryService}. This constructor should only
|
* Constructs a new {@link RepositoryService}. This constructor should only
|
||||||
* be called from the {@link RepositoryServiceFactory}.
|
* be called from the {@link RepositoryServiceFactory}.
|
||||||
* @param cacheManager cache manager
|
* @param cacheManager cache manager
|
||||||
* @param provider implementation for {@link RepositoryServiceProvider}
|
* @param provider implementation for {@link RepositoryServiceProvider}
|
||||||
* @param repository the repository
|
* @param repository the repository
|
||||||
* @param workdirProvider
|
* @param workdirProvider provider for workdirs
|
||||||
|
* @param eMail utility to compute email addresses if missing
|
||||||
*/
|
*/
|
||||||
RepositoryService(CacheManager cacheManager,
|
RepositoryService(CacheManager cacheManager,
|
||||||
RepositoryServiceProvider provider, Repository repository,
|
RepositoryServiceProvider provider,
|
||||||
|
Repository repository,
|
||||||
PreProcessorUtil preProcessorUtil,
|
PreProcessorUtil preProcessorUtil,
|
||||||
@SuppressWarnings("rawtypes") Set<ScmProtocolProvider> protocolProviders,
|
@SuppressWarnings({"rawtypes", "java:S3740"}) Set<ScmProtocolProvider> protocolProviders,
|
||||||
WorkdirProvider workdirProvider
|
WorkdirProvider workdirProvider,
|
||||||
) {
|
@Nullable EMail eMail) {
|
||||||
this.cacheManager = cacheManager;
|
this.cacheManager = cacheManager;
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.preProcessorUtil = preProcessorUtil;
|
this.preProcessorUtil = preProcessorUtil;
|
||||||
this.protocolProviders = protocolProviders;
|
this.protocolProviders = protocolProviders;
|
||||||
this.workdirProvider = workdirProvider;
|
this.workdirProvider = workdirProvider;
|
||||||
|
this.eMail = eMail;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -397,7 +405,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
LOG.debug("create merge command for repository {}",
|
LOG.debug("create merge command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new MergeCommandBuilder(provider.getMergeCommand());
|
return new MergeCommandBuilder(provider.getMergeCommand(), eMail);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -418,7 +426,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
LOG.debug("create modify command for repository {}",
|
LOG.debug("create modify command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new ModifyCommandBuilder(provider.getModifyCommand(), workdirProvider);
|
return new ModifyCommandBuilder(provider.getModifyCommand(), workdirProvider, eMail);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -448,7 +456,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
.map(this::createProviderInstanceForRepository);
|
.map(this::createProviderInstanceForRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings({"rawtypes", "java:S3740"})
|
||||||
private ScmProtocol createProviderInstanceForRepository(ScmProtocolProvider protocolProvider) {
|
private ScmProtocol createProviderInstanceForRepository(ScmProtocolProvider protocolProvider) {
|
||||||
return protocolProvider.get(repository);
|
return protocolProvider.get(repository);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ import sonia.scm.cache.Cache;
|
|||||||
import sonia.scm.cache.CacheManager;
|
import sonia.scm.cache.CacheManager;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
import sonia.scm.repository.BranchCreatedEvent;
|
|
||||||
import sonia.scm.repository.ClearRepositoryCacheEvent;
|
import sonia.scm.repository.ClearRepositoryCacheEvent;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
import sonia.scm.repository.PostReceiveRepositoryHookEvent;
|
||||||
@@ -58,7 +57,9 @@ import sonia.scm.repository.work.WorkdirProvider;
|
|||||||
import sonia.scm.security.PublicKeyCreatedEvent;
|
import sonia.scm.security.PublicKeyCreatedEvent;
|
||||||
import sonia.scm.security.PublicKeyDeletedEvent;
|
import sonia.scm.security.PublicKeyDeletedEvent;
|
||||||
import sonia.scm.security.ScmSecurityException;
|
import sonia.scm.security.ScmSecurityException;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
@@ -115,7 +116,17 @@ public final class RepositoryServiceFactory {
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(RepositoryServiceFactory.class);
|
LoggerFactory.getLogger(RepositoryServiceFactory.class);
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
private final CacheManager cacheManager;
|
||||||
|
private final RepositoryManager repositoryManager;
|
||||||
|
private final Set<RepositoryServiceResolver> resolvers;
|
||||||
|
private final PreProcessorUtil preProcessorUtil;
|
||||||
|
@SuppressWarnings({"rawtypes", "java:S3740"})
|
||||||
|
private final Set<ScmProtocolProvider> protocolProviders;
|
||||||
|
private final WorkdirProvider workdirProvider;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private final EMail eMail;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new {@link RepositoryServiceFactory}. This constructor
|
* Constructs a new {@link RepositoryServiceFactory}. This constructor
|
||||||
@@ -127,40 +138,67 @@ public final class RepositoryServiceFactory {
|
|||||||
* @param repositoryManager manager for repositories
|
* @param repositoryManager manager for repositories
|
||||||
* @param resolvers a set of {@link RepositoryServiceResolver}
|
* @param resolvers a set of {@link RepositoryServiceResolver}
|
||||||
* @param preProcessorUtil helper object for pre processor handling
|
* @param preProcessorUtil helper object for pre processor handling
|
||||||
* @param protocolProviders
|
* @param protocolProviders providers for repository protocols
|
||||||
* @param workdirProvider
|
* @param workdirProvider provider for working directories
|
||||||
|
*
|
||||||
|
* @deprecated use {@link RepositoryServiceFactory#RepositoryServiceFactory(CacheManager, RepositoryManager, Set, PreProcessorUtil, Set, WorkdirProvider, EMail)} instead
|
||||||
* @since 1.21
|
* @since 1.21
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Deprecated
|
||||||
public RepositoryServiceFactory(ScmConfiguration configuration,
|
public RepositoryServiceFactory(ScmConfiguration configuration,
|
||||||
CacheManager cacheManager, RepositoryManager repositoryManager,
|
CacheManager cacheManager, RepositoryManager repositoryManager,
|
||||||
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
||||||
@SuppressWarnings("rawtypes") Set<ScmProtocolProvider> protocolProviders, WorkdirProvider workdirProvider) {
|
@SuppressWarnings({"rawtypes", "java:S3740"}) Set<ScmProtocolProvider> protocolProviders,
|
||||||
|
WorkdirProvider workdirProvider) {
|
||||||
this(
|
this(
|
||||||
configuration, cacheManager, repositoryManager, resolvers,
|
cacheManager, repositoryManager, resolvers,
|
||||||
preProcessorUtil, protocolProviders, workdirProvider, ScmEventBus.getInstance()
|
preProcessorUtil, protocolProviders, workdirProvider, null, ScmEventBus.getInstance()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@link RepositoryServiceFactory}. This constructor
|
||||||
|
* should not be called manually, it should only be used by the injection
|
||||||
|
* container.
|
||||||
|
*
|
||||||
|
* @param cacheManager cache manager
|
||||||
|
* @param repositoryManager manager for repositories
|
||||||
|
* @param resolvers a set of {@link RepositoryServiceResolver}
|
||||||
|
* @param preProcessorUtil helper object for pre processor handling
|
||||||
|
* @param protocolProviders providers for repository protocols
|
||||||
|
* @param workdirProvider provider for working directories
|
||||||
|
* @param eMail handling user emails
|
||||||
|
* @since 2.8.0
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
public RepositoryServiceFactory(CacheManager cacheManager, RepositoryManager repositoryManager,
|
||||||
|
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
||||||
|
@SuppressWarnings({"rawtypes", "java:S3740"}) Set<ScmProtocolProvider> protocolProviders,
|
||||||
|
WorkdirProvider workdirProvider, EMail eMail) {
|
||||||
|
this(
|
||||||
|
cacheManager, repositoryManager, resolvers,
|
||||||
|
preProcessorUtil, protocolProviders, workdirProvider,
|
||||||
|
eMail, ScmEventBus.getInstance()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
RepositoryServiceFactory(ScmConfiguration configuration,
|
@SuppressWarnings("java:S107") // to keep backward compatibility, we can not reduce amount of parameters
|
||||||
CacheManager cacheManager, RepositoryManager repositoryManager,
|
RepositoryServiceFactory(CacheManager cacheManager, RepositoryManager repositoryManager,
|
||||||
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
Set<RepositoryServiceResolver> resolvers, PreProcessorUtil preProcessorUtil,
|
||||||
Set<ScmProtocolProvider> protocolProviders, WorkdirProvider workdirProvider,
|
@SuppressWarnings({"rawtypes", "java:S3740"}) Set<ScmProtocolProvider> protocolProviders,
|
||||||
ScmEventBus eventBus) {
|
WorkdirProvider workdirProvider, @Nullable EMail eMail, ScmEventBus eventBus) {
|
||||||
this.configuration = configuration;
|
|
||||||
this.cacheManager = cacheManager;
|
this.cacheManager = cacheManager;
|
||||||
this.repositoryManager = repositoryManager;
|
this.repositoryManager = repositoryManager;
|
||||||
this.resolvers = resolvers;
|
this.resolvers = resolvers;
|
||||||
this.preProcessorUtil = preProcessorUtil;
|
this.preProcessorUtil = preProcessorUtil;
|
||||||
this.protocolProviders = protocolProviders;
|
this.protocolProviders = protocolProviders;
|
||||||
this.workdirProvider = workdirProvider;
|
this.workdirProvider = workdirProvider;
|
||||||
|
this.eMail = eMail;
|
||||||
|
|
||||||
eventBus.register(new CacheClearHook(cacheManager));
|
eventBus.register(new CacheClearHook(cacheManager));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new RepositoryService for the given repository.
|
* Creates a new RepositoryService for the given repository.
|
||||||
*
|
*
|
||||||
@@ -246,7 +284,7 @@ public final class RepositoryServiceFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
service = new RepositoryService(cacheManager, provider, repository,
|
service = new RepositoryService(cacheManager, provider, repository,
|
||||||
preProcessorUtil, protocolProviders, workdirProvider);
|
preProcessorUtil, protocolProviders, workdirProvider, eMail);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -259,8 +297,6 @@ public final class RepositoryServiceFactory {
|
|||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hook and listener to clear all relevant repository caches.
|
* Hook and listener to clear all relevant repository caches.
|
||||||
*/
|
*/
|
||||||
@@ -284,8 +320,6 @@ public final class RepositoryServiceFactory {
|
|||||||
this.caches.add(cacheManager.getCache(BranchesCommandBuilder.CACHE_NAME));
|
this.caches.add(cacheManager.getCache(BranchesCommandBuilder.CACHE_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods ------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear caches on explicit repository cache clear event.
|
* Clear caches on explicit repository cache clear event.
|
||||||
*
|
*
|
||||||
@@ -347,35 +381,4 @@ public final class RepositoryServiceFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* cache manager
|
|
||||||
*/
|
|
||||||
private final CacheManager cacheManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* scm-manager configuration
|
|
||||||
*/
|
|
||||||
private final ScmConfiguration configuration;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* pre processor util
|
|
||||||
*/
|
|
||||||
private final PreProcessorUtil preProcessorUtil;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* repository manager
|
|
||||||
*/
|
|
||||||
private final RepositoryManager repositoryManager;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* service resolvers
|
|
||||||
*/
|
|
||||||
private final Set<RepositoryServiceResolver> resolvers;
|
|
||||||
|
|
||||||
private Set<ScmProtocolProvider> protocolProviders;
|
|
||||||
|
|
||||||
private final WorkdirProvider workdirProvider;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,22 +27,29 @@ package sonia.scm.repository.util;
|
|||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
public class AuthorUtil {
|
public class AuthorUtil {
|
||||||
|
|
||||||
public static void setAuthorIfNotAvailable(CommandWithAuthor request) {
|
public static void setAuthorIfNotAvailable(CommandWithAuthor request) {
|
||||||
|
setAuthorIfNotAvailable(request, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setAuthorIfNotAvailable(CommandWithAuthor request, @Nullable EMail eMail) {
|
||||||
if (request.getAuthor() == null) {
|
if (request.getAuthor() == null) {
|
||||||
request.setAuthor(createAuthorFromSubject());
|
request.setAuthor(createAuthorFromSubject(eMail));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Person createAuthorFromSubject() {
|
private static Person createAuthorFromSubject(@Nullable EMail eMail) {
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
User user = subject.getPrincipals().oneByType(User.class);
|
User user = subject.getPrincipals().oneByType(User.class);
|
||||||
String name = user.getDisplayName();
|
String name = user.getDisplayName();
|
||||||
String email = user.getMail();
|
String mailAddress = eMail != null ? eMail.getMailOrFallback(user) : user.getMail();
|
||||||
return new Person(name, email);
|
return new Person(name, mailAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface CommandWithAuthor {
|
public interface CommandWithAuthor {
|
||||||
|
|||||||
71
scm-core/src/main/java/sonia/scm/user/EMail.java
Normal file
71
scm-core/src/main/java/sonia/scm/user/EMail.java
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
import sonia.scm.util.ValidationUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Email is able to resolve email addresses of users.
|
||||||
|
*
|
||||||
|
* @since 2.8.0
|
||||||
|
*/
|
||||||
|
public class EMail {
|
||||||
|
|
||||||
|
private final ScmConfiguration scmConfiguration;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public EMail(ScmConfiguration scmConfiguration) {
|
||||||
|
this.scmConfiguration = scmConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the email address of the given user or a generated fallback address.
|
||||||
|
* @param user user to resolve address from
|
||||||
|
* @return email address or fallback
|
||||||
|
*/
|
||||||
|
public String getMailOrFallback(User user) {
|
||||||
|
if (Strings.isNullOrEmpty(user.getMail())) {
|
||||||
|
if (isMailUsedAsId(user)) {
|
||||||
|
return user.getId();
|
||||||
|
} else {
|
||||||
|
return createFallbackMail(user);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return user.getMail();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isMailUsedAsId(User user) {
|
||||||
|
return ValidationUtil.isMailAddressValid(user.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private String createFallbackMail(User user) {
|
||||||
|
return user.getId() + "@" + scmConfiguration.getMailDomainName();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,12 @@
|
|||||||
package sonia.scm.repository.api;
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
@@ -34,10 +39,12 @@ import org.mockito.Mock;
|
|||||||
import org.mockito.invocation.InvocationOnMock;
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.spi.ModifyCommand;
|
import sonia.scm.repository.spi.ModifyCommand;
|
||||||
import sonia.scm.repository.spi.ModifyCommandRequest;
|
import sonia.scm.repository.spi.ModifyCommandRequest;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -50,15 +57,19 @@ import java.util.List;
|
|||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.Mockito.doAnswer;
|
import static org.mockito.Mockito.doAnswer;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class ModifyCommandBuilderTest {
|
class ModifyCommandBuilderTest {
|
||||||
|
|
||||||
|
private static final ScmConfiguration SCM_CONFIGURATION = new ScmConfiguration();
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
ModifyCommand command;
|
ModifyCommand command;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -73,7 +84,7 @@ class ModifyCommandBuilderTest {
|
|||||||
void initWorkdir(@TempDir Path temp) throws IOException {
|
void initWorkdir(@TempDir Path temp) throws IOException {
|
||||||
workdir = Files.createDirectory(temp.resolve("workdir"));
|
workdir = Files.createDirectory(temp.resolve("workdir"));
|
||||||
lenient().when(workdirProvider.createNewWorkdir()).thenReturn(workdir.toFile());
|
lenient().when(workdirProvider.createNewWorkdir()).thenReturn(workdir.toFile());
|
||||||
commandBuilder = new ModifyCommandBuilder(command, workdirProvider);
|
commandBuilder = new ModifyCommandBuilder(command, workdirProvider, new EMail(SCM_CONFIGURATION));
|
||||||
}
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
@@ -89,6 +100,48 @@ class ModifyCommandBuilderTest {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ModifyCommandBuilder initCommand() {
|
||||||
|
return commandBuilder
|
||||||
|
.setBranch("branch")
|
||||||
|
.setCommitMessage("message");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void mockLoggedInUser(User loggedInUser) {
|
||||||
|
Subject subject = mock(Subject.class);
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
PrincipalCollection principals = mock(PrincipalCollection.class);
|
||||||
|
when(subject.getPrincipals()).thenReturn(principals);
|
||||||
|
when(principals.oneByType(User.class)).thenReturn(loggedInUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void unbindSubjec() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class ExtractContent implements Answer {
|
||||||
|
|
||||||
|
private final List<String> contentCaptor;
|
||||||
|
|
||||||
|
public ExtractContent(List<String> contentCaptor) {
|
||||||
|
this.contentCaptor = contentCaptor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
return contentCaptor.add(Files.readAllLines(((File) invocation.getArgument(1)).toPath()).get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nested
|
||||||
|
class WithUserWithMail {
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initSubject() {
|
||||||
|
User loggedInUser = new User("dent", "Arthur", "dent@hitchhiker.com");
|
||||||
|
mockLoggedInUser(loggedInUser);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnTargetRevisionFromCommit() {
|
void shouldReturnTargetRevisionFromCommit() {
|
||||||
String targetRevision = initCommand()
|
String targetRevision = initCommand()
|
||||||
@@ -198,13 +251,6 @@ class ModifyCommandBuilderTest {
|
|||||||
assertThat(contentCaptor).contains("content");
|
assertThat(contentCaptor).contains("content");
|
||||||
}
|
}
|
||||||
|
|
||||||
private ModifyCommandBuilder initCommand() {
|
|
||||||
return commandBuilder
|
|
||||||
.setBranch("branch")
|
|
||||||
.setCommitMessage("message")
|
|
||||||
.setAuthor(new Person());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldDeleteTemporaryFiles(@TempDir Path temp) throws IOException {
|
void shouldDeleteTemporaryFiles(@TempDir Path temp) throws IOException {
|
||||||
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
ArgumentCaptor<String> nameCaptor = ArgumentCaptor.forClass(String.class);
|
||||||
@@ -218,16 +264,39 @@ class ModifyCommandBuilderTest {
|
|||||||
assertThat(Files.list(temp)).isEmpty();
|
assertThat(Files.list(temp)).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class ExtractContent implements Answer {
|
@Test
|
||||||
private final List<String> contentCaptor;
|
void shouldUseMailFromUser() throws IOException {
|
||||||
|
initCommand()
|
||||||
|
.modifyFile("toBeModified").withData(ByteSource.wrap("content".getBytes()))
|
||||||
|
.execute();
|
||||||
|
|
||||||
public ExtractContent(List<String> contentCaptor) {
|
verify(command).execute(argThat(modifyCommandRequest -> {
|
||||||
this.contentCaptor = contentCaptor;
|
assertThat(modifyCommandRequest.getAuthor().getMail()).isEqualTo("dent@hitchhiker.com");
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Nested
|
||||||
public Object answer(InvocationOnMock invocation) throws Throwable {
|
class WithUserWithoutMail {
|
||||||
return contentCaptor.add(Files.readAllLines(((File) invocation.getArgument(1)).toPath()).get(0));
|
|
||||||
|
@BeforeEach
|
||||||
|
void initSubject() {
|
||||||
|
User loggedInUser = new User("dent", "Arthur", null);
|
||||||
|
mockLoggedInUser(loggedInUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseMailFromUser() throws IOException {
|
||||||
|
SCM_CONFIGURATION.setMailDomainName("heart-of-gold.local");
|
||||||
|
initCommand()
|
||||||
|
.modifyFile("toBeModified").withData(ByteSource.wrap("content".getBytes()))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
verify(command).execute(argThat(modifyCommandRequest -> {
|
||||||
|
assertThat(modifyCommandRequest.getAuthor().getMail()).isEqualTo("dent@heart-of-gold.local");
|
||||||
|
return true;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import sonia.scm.repository.RepositoryManager;
|
|||||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||||
import sonia.scm.repository.spi.RepositoryServiceResolver;
|
import sonia.scm.repository.spi.RepositoryServiceResolver;
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
@@ -56,9 +57,6 @@ import static org.mockito.Mockito.when;
|
|||||||
@ExtendWith(MockitoExtension.class)
|
@ExtendWith(MockitoExtension.class)
|
||||||
class RepositoryServiceFactoryTest {
|
class RepositoryServiceFactoryTest {
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ScmConfiguration configuration;
|
|
||||||
|
|
||||||
@Mock(answer = Answers.RETURNS_MOCKS)
|
@Mock(answer = Answers.RETURNS_MOCKS)
|
||||||
private CacheManager cacheManager;
|
private CacheManager cacheManager;
|
||||||
|
|
||||||
@@ -94,8 +92,9 @@ class RepositoryServiceFactoryTest {
|
|||||||
builder.add(repositoryServiceResolver);
|
builder.add(repositoryServiceResolver);
|
||||||
}
|
}
|
||||||
return new RepositoryServiceFactory(
|
return new RepositoryServiceFactory(
|
||||||
configuration, cacheManager, repositoryManager, builder.build(),
|
cacheManager, repositoryManager, builder.build(),
|
||||||
preProcessorUtil, ImmutableSet.of(), workdirProvider, eventBus
|
preProcessorUtil, ImmutableSet.of(), workdirProvider,
|
||||||
|
new EMail(new ScmConfiguration()), eventBus
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,9 +25,11 @@
|
|||||||
package sonia.scm.repository.api;
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.spi.HttpScmProtocol;
|
import sonia.scm.repository.spi.HttpScmProtocol;
|
||||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
|
||||||
import javax.servlet.ServletConfig;
|
import javax.servlet.ServletConfig;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
@@ -46,9 +48,11 @@ public class RepositoryServiceTest {
|
|||||||
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
|
private final RepositoryServiceProvider provider = mock(RepositoryServiceProvider.class);
|
||||||
private final Repository repository = new Repository("", "git", "space", "repo");
|
private final Repository repository = new Repository("", "git", "space", "repo");
|
||||||
|
|
||||||
|
private final EMail eMail = new EMail(new ScmConfiguration());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnMatchingProtocolsFromProvider() {
|
public void shouldReturnMatchingProtocolsFromProvider() {
|
||||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null);
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
||||||
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
Stream<ScmProtocol> supportedProtocols = repositoryService.getSupportedProtocols();
|
||||||
|
|
||||||
assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1);
|
assertThat(sizeOf(supportedProtocols.collect(Collectors.toList()))).isEqualTo(1);
|
||||||
@@ -56,7 +60,7 @@ public class RepositoryServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFindKnownProtocol() {
|
public void shouldFindKnownProtocol() {
|
||||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null);
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
||||||
|
|
||||||
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
HttpScmProtocol protocol = repositoryService.getProtocol(HttpScmProtocol.class);
|
||||||
|
|
||||||
@@ -65,11 +69,9 @@ public class RepositoryServiceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFailForUnknownProtocol() {
|
public void shouldFailForUnknownProtocol() {
|
||||||
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null);
|
RepositoryService repositoryService = new RepositoryService(null, provider, repository, null, Collections.singleton(new DummyScmProtocolProvider()), null, eMail);
|
||||||
|
|
||||||
assertThrows(IllegalArgumentException.class, () -> {
|
assertThrows(IllegalArgumentException.class, () -> repositoryService.getProtocol(UnknownScmProtocol.class));
|
||||||
repositoryService.getProtocol(UnknownScmProtocol.class);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DummyHttpProtocol extends HttpScmProtocol {
|
private static class DummyHttpProtocol extends HttpScmProtocol {
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.util;
|
||||||
|
|
||||||
|
import org.apache.shiro.subject.Subject;
|
||||||
|
import org.apache.shiro.util.ThreadContext;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Answers;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.repository.Person;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class AuthorUtilTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EMail eMail;
|
||||||
|
|
||||||
|
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
|
||||||
|
private Subject subject;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpSubject() {
|
||||||
|
ThreadContext.bind(subject);
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void tearDownSubject() {
|
||||||
|
ThreadContext.unbindSubject();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateMailAddressFromEmail() {
|
||||||
|
User trillian = new User("trillian");
|
||||||
|
when(subject.getPrincipals().oneByType(User.class)).thenReturn(trillian);
|
||||||
|
when(eMail.getMailOrFallback(trillian)).thenReturn("tricia@hitchhicker.com");
|
||||||
|
|
||||||
|
Command command = new Command(null);
|
||||||
|
AuthorUtil.setAuthorIfNotAvailable(command, eMail);
|
||||||
|
|
||||||
|
assertThat(command.getAuthor().getMail()).isEqualTo("tricia@hitchhicker.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUseUsersMailAddressWithoutEMail() {
|
||||||
|
User trillian = new User("trillian", "Trillian", "trillian.mcmillan@hitchhiker.com");
|
||||||
|
when(subject.getPrincipals().oneByType(User.class)).thenReturn(trillian);
|
||||||
|
|
||||||
|
Command command = new Command(null);
|
||||||
|
AuthorUtil.setAuthorIfNotAvailable(command);
|
||||||
|
|
||||||
|
assertThat(command.getAuthor().getMail()).isEqualTo("trillian.mcmillan@hitchhiker.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldKeepExistingAuthor() {
|
||||||
|
Person person = new Person("Trillian McMillan", "trillian.mcmillian@hitchhiker.com");
|
||||||
|
|
||||||
|
Command command = new Command(person);
|
||||||
|
AuthorUtil.setAuthorIfNotAvailable(command);
|
||||||
|
|
||||||
|
assertThat(command.getAuthor()).isSameAs(person);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Command implements AuthorUtil.CommandWithAuthor {
|
||||||
|
|
||||||
|
private Person person;
|
||||||
|
|
||||||
|
public Command(Person person) {
|
||||||
|
this.person = person;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Person getAuthor() {
|
||||||
|
return person;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthor(Person person) {
|
||||||
|
this.person = person;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
62
scm-core/src/test/java/sonia/scm/user/EMailTest.java
Normal file
62
scm-core/src/test/java/sonia/scm/user/EMailTest.java
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import sonia.scm.config.ScmConfiguration;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
class EMailTest {
|
||||||
|
|
||||||
|
EMail eMail = new EMail(new ScmConfiguration());
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUserUsersAddressIfAvailable() {
|
||||||
|
User user = new User("dent", "Arthur Dent", "arthur@hitchhiker.com");
|
||||||
|
|
||||||
|
String mailAddress = eMail.getMailOrFallback(user);
|
||||||
|
|
||||||
|
assertThat(mailAddress).isEqualTo("arthur@hitchhiker.com");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateAddressIfNoneAvailable() {
|
||||||
|
User user = new User("dent", "Arthur Dent", "");
|
||||||
|
|
||||||
|
String mailAddress = eMail.getMailOrFallback(user);
|
||||||
|
|
||||||
|
assertThat(mailAddress).isEqualTo("dent@scm-manager.local");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUserUsersIdIfItLooksLikeAnMailAddress() {
|
||||||
|
User user = new User("dent@hitchhiker.com", "Arthur Dent", "");
|
||||||
|
|
||||||
|
String mailAddress = eMail.getMailOrFallback(user);
|
||||||
|
|
||||||
|
assertThat(mailAddress).isEqualTo("dent@hitchhiker.com");
|
||||||
|
}
|
||||||
|
}
|
||||||
60
scm-ui/ui-components/src/repos/CommitAuthor.tsx
Normal file
60
scm-ui/ui-components/src/repos/CommitAuthor.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React, { FC } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import Notification from "../Notification";
|
||||||
|
import { Me } from "@scm-manager/ui-types";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
// props from global state
|
||||||
|
me: Me;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CommitAuthor: FC<Props> = ({ me }) => {
|
||||||
|
const [t] = useTranslation("repos");
|
||||||
|
|
||||||
|
const mail = me.mail ? me.mail : me.fallbackMail;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!me.mail && <Notification type="warning">{t("commit.commitAuthor.noMail")}</Notification>}
|
||||||
|
<span className="mb-2">
|
||||||
|
<strong>{t("commit.commitAuthor.author")}</strong> {`${me.displayName} <${mail}>`}
|
||||||
|
</span>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = (state: any) => {
|
||||||
|
const { auth } = state;
|
||||||
|
const me = auth.me;
|
||||||
|
|
||||||
|
return {
|
||||||
|
me
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(CommitAuthor);
|
||||||
@@ -52,6 +52,7 @@ export { default as RepositoryAvatar } from "./RepositoryAvatar";
|
|||||||
export { default as RepositoryEntry } from "./RepositoryEntry";
|
export { default as RepositoryEntry } from "./RepositoryEntry";
|
||||||
export { default as RepositoryEntryLink } from "./RepositoryEntryLink";
|
export { default as RepositoryEntryLink } from "./RepositoryEntryLink";
|
||||||
export { default as JumpToFileButton } from "./JumpToFileButton";
|
export { default as JumpToFileButton } from "./JumpToFileButton";
|
||||||
|
export { default as CommitAuthor } from "./CommitAuthor";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
File,
|
File,
|
||||||
|
|||||||
@@ -49,5 +49,6 @@ export type Config = {
|
|||||||
namespaceStrategy: string;
|
namespaceStrategy: string;
|
||||||
loginInfoUrl: string;
|
loginInfoUrl: string;
|
||||||
releaseFeedUrl: string;
|
releaseFeedUrl: string;
|
||||||
|
mailDomainName: string;
|
||||||
_links: Links;
|
_links: Links;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ import { Links } from "./hal";
|
|||||||
export type Me = {
|
export type Me = {
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
mail: string;
|
mail?: string;
|
||||||
|
fallbackMail?: string;
|
||||||
groups: string[];
|
groups: string[];
|
||||||
_links: Links;
|
_links: Links;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,13 +27,13 @@ import { Links } from "./hal";
|
|||||||
export type DisplayedUser = {
|
export type DisplayedUser = {
|
||||||
id: string;
|
id: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
mail: string;
|
mail?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type User = {
|
export type User = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
name: string;
|
name: string;
|
||||||
mail: string;
|
mail?: string;
|
||||||
password: string;
|
password: string;
|
||||||
active: boolean;
|
active: boolean;
|
||||||
type?: string;
|
type?: string;
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
|
"skip-failed-authenticators": "Fehlgeschlagene Authentifizierer überspringen",
|
||||||
"plugin-url": "Plugin Center URL",
|
"plugin-url": "Plugin Center URL",
|
||||||
"release-feed-url": "Release Feed URL",
|
"release-feed-url": "Release Feed URL",
|
||||||
|
"mail-domain-name": "Fallback E-Mail Domain Name",
|
||||||
"enabled-xsrf-protection": "XSRF Protection aktivieren",
|
"enabled-xsrf-protection": "XSRF Protection aktivieren",
|
||||||
"enabled-user-converter": "Benutzer Konverter aktivieren",
|
"enabled-user-converter": "Benutzer Konverter aktivieren",
|
||||||
"namespace-strategy": "Namespace Strategie",
|
"namespace-strategy": "Namespace Strategie",
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.",
|
"dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.",
|
||||||
"pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
|
"pluginUrlHelpText": "Die URL der Plugin Center API. Beschreibung der Platzhalter: version = SCM-Manager Version; os = Betriebssystem; arch = Architektur",
|
||||||
"releaseFeedUrlHelpText": "Die URL des RSS Release Feed des SCM-Manager. Darüber wird über die neue SCM-Manager Version informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.",
|
"releaseFeedUrlHelpText": "Die URL des RSS Release Feed des SCM-Manager. Darüber wird über die neue SCM-Manager Version informiert. Um diese Funktion zu deaktivieren lassen Sie dieses Feld leer.",
|
||||||
|
"mailDomainNameHelpText": "Dieser Domain Name wird genutzt, wenn für einen User eine E-Mail-Adresse benötigt wird, für den keine hinterlegt ist. Diese Domain wird nicht zum Versenden von E-Mails genutzt und auch keine anderweitige Verbindung aufgebaut.",
|
||||||
"enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.",
|
"enableForwardingHelpText": "mod_proxy Port Weiterleitung aktivieren.",
|
||||||
"disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.",
|
"disableGroupingGridHelpText": "Repository Gruppen deaktivieren. Nach einer Änderung an dieser Einstellung muss die Seite komplett neu geladen werden.",
|
||||||
"allowAnonymousAccessHelpText": "Anonyme Benutzer haben Zugriff auf freigegebene Repositories.",
|
"allowAnonymousAccessHelpText": "Anonyme Benutzer haben Zugriff auf freigegebene Repositories.",
|
||||||
|
|||||||
@@ -135,6 +135,12 @@
|
|||||||
"sources": "Sources"
|
"sources": "Sources"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"commit": {
|
||||||
|
"commitAuthor": {
|
||||||
|
"author": "Autor",
|
||||||
|
"noMail": "Für den aktuellen Benutzer existiert keine E-Mail-Adresse. Es wird die unten angezeigte generierte Adresse genutzt."
|
||||||
|
}
|
||||||
|
},
|
||||||
"repositoryForm": {
|
"repositoryForm": {
|
||||||
"subtitle": "Repository bearbeiten",
|
"subtitle": "Repository bearbeiten",
|
||||||
"submit": "Speichern",
|
"submit": "Speichern",
|
||||||
@@ -269,4 +275,4 @@
|
|||||||
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",
|
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",
|
||||||
"dragAndDrop": "Sie können Ihre Datei auch direkt in die Dropzone ziehen."
|
"dragAndDrop": "Sie können Ihre Datei auch direkt in die Dropzone ziehen."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
"skip-failed-authenticators": "Skip Failed Authenticators",
|
"skip-failed-authenticators": "Skip Failed Authenticators",
|
||||||
"plugin-url": "Plugin Center URL",
|
"plugin-url": "Plugin Center URL",
|
||||||
"release-feed-url": "Release Feed URL",
|
"release-feed-url": "Release Feed URL",
|
||||||
|
"mail-domain-name": "Fallback Mail Domain Name",
|
||||||
"enabled-xsrf-protection": "Enabled XSRF Protection",
|
"enabled-xsrf-protection": "Enabled XSRF Protection",
|
||||||
"enabled-user-converter": "Enabled User Converter",
|
"enabled-user-converter": "Enabled User Converter",
|
||||||
"namespace-strategy": "Namespace Strategy",
|
"namespace-strategy": "Namespace Strategy",
|
||||||
@@ -63,6 +64,7 @@
|
|||||||
"dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.",
|
"dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.",
|
||||||
"pluginUrlHelpText": "The url of the Plugin Center API. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture",
|
"pluginUrlHelpText": "The url of the Plugin Center API. Explanation of the placeholders: version = SCM-Manager Version; os = Operation System; arch = Architecture",
|
||||||
"releaseFeedUrlHelpText": "The url of the RSS Release Feed for SCM-Manager. This provides up-to-date version information. To disable this feature just leave the url blank.",
|
"releaseFeedUrlHelpText": "The url of the RSS Release Feed for SCM-Manager. This provides up-to-date version information. To disable this feature just leave the url blank.",
|
||||||
|
"mailDomainNameHelpText": "This domain name will be used to create email addresses for users without one when needed. It will not be used to send mails nor will be accessed otherwise.",
|
||||||
"enableForwardingHelpText": "Enable mod_proxy port forwarding.",
|
"enableForwardingHelpText": "Enable mod_proxy port forwarding.",
|
||||||
"disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.",
|
"disableGroupingGridHelpText": "Disable repository Groups. A complete page reload is required after a change of this value.",
|
||||||
"allowAnonymousAccessHelpText": "Anonymous users have access on granted repositories.",
|
"allowAnonymousAccessHelpText": "Anonymous users have access on granted repositories.",
|
||||||
|
|||||||
@@ -135,6 +135,12 @@
|
|||||||
"count_plural": "{{count}} Contributors"
|
"count_plural": "{{count}} Contributors"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"commit": {
|
||||||
|
"commitAuthor": {
|
||||||
|
"author": "Author",
|
||||||
|
"noMail": "We have found no email address for your current user. We will use the generated address shown below."
|
||||||
|
}
|
||||||
|
},
|
||||||
"repositoryForm": {
|
"repositoryForm": {
|
||||||
"subtitle": "Edit Repository",
|
"subtitle": "Edit Repository",
|
||||||
"submit": "Save",
|
"submit": "Save",
|
||||||
|
|||||||
@@ -145,6 +145,7 @@ class ConfigForm extends React.Component<Props, State> {
|
|||||||
skipFailedAuthenticators={config.skipFailedAuthenticators}
|
skipFailedAuthenticators={config.skipFailedAuthenticators}
|
||||||
pluginUrl={config.pluginUrl}
|
pluginUrl={config.pluginUrl}
|
||||||
releaseFeedUrl={config.releaseFeedUrl}
|
releaseFeedUrl={config.releaseFeedUrl}
|
||||||
|
mailDomainName={config.mailDomainName}
|
||||||
enabledXsrfProtection={config.enabledXsrfProtection}
|
enabledXsrfProtection={config.enabledXsrfProtection}
|
||||||
enabledUserConverter={config.enabledUserConverter}
|
enabledUserConverter={config.enabledUserConverter}
|
||||||
namespaceStrategy={config.namespaceStrategy}
|
namespaceStrategy={config.namespaceStrategy}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ type Props = WithTranslation & {
|
|||||||
skipFailedAuthenticators: boolean;
|
skipFailedAuthenticators: boolean;
|
||||||
pluginUrl: string;
|
pluginUrl: string;
|
||||||
releaseFeedUrl: string;
|
releaseFeedUrl: string;
|
||||||
|
mailDomainName: string;
|
||||||
enabledXsrfProtection: boolean;
|
enabledXsrfProtection: boolean;
|
||||||
enabledUserConverter: boolean;
|
enabledUserConverter: boolean;
|
||||||
namespaceStrategy: string;
|
namespaceStrategy: string;
|
||||||
@@ -52,6 +53,7 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
loginInfoUrl,
|
loginInfoUrl,
|
||||||
pluginUrl,
|
pluginUrl,
|
||||||
releaseFeedUrl,
|
releaseFeedUrl,
|
||||||
|
mailDomainName,
|
||||||
enabledXsrfProtection,
|
enabledXsrfProtection,
|
||||||
enabledUserConverter,
|
enabledUserConverter,
|
||||||
anonymousMode,
|
anonymousMode,
|
||||||
@@ -140,6 +142,15 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
helpText={t("help.releaseFeedUrlHelpText")}
|
helpText={t("help.releaseFeedUrlHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="column is-half">
|
||||||
|
<InputField
|
||||||
|
label={t("general-settings.mail-domain-name")}
|
||||||
|
onChange={this.handleMailDomainNameChange}
|
||||||
|
value={mailDomainName}
|
||||||
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.mailDomainNameHelpText")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="column is-half">
|
<div className="column is-half">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label={t("general-settings.enabled-user-converter")}
|
label={t("general-settings.enabled-user-converter")}
|
||||||
@@ -179,6 +190,9 @@ class GeneralSettings extends React.Component<Props> {
|
|||||||
handleReleaseFeedUrlChange = (value: string) => {
|
handleReleaseFeedUrlChange = (value: string) => {
|
||||||
this.props.onChange(true, value, "releaseFeedUrl");
|
this.props.onChange(true, value, "releaseFeedUrl");
|
||||||
};
|
};
|
||||||
|
handleMailDomainNameChange = (value: string) => {
|
||||||
|
this.props.onChange(true, value, "mailDomainName");
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withTranslation("config")(GeneralSettings);
|
export default withTranslation("config")(GeneralSettings);
|
||||||
|
|||||||
@@ -264,7 +264,7 @@ class UserForm extends React.Component<Props, State> {
|
|||||||
|
|
||||||
handleEmailChange = (mail: string) => {
|
handleEmailChange = (mail: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
mailValidationError: !validator.isMailValid(mail),
|
mailValidationError: !!mail && !validator.isMailValid(mail),
|
||||||
user: {
|
user: {
|
||||||
...this.state.user,
|
...this.state.user,
|
||||||
mail
|
mail
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public class ConfigDto extends HalRepresentation {
|
|||||||
private String namespaceStrategy;
|
private String namespaceStrategy;
|
||||||
private String loginInfoUrl;
|
private String loginInfoUrl;
|
||||||
private String releaseFeedUrl;
|
private String releaseFeedUrl;
|
||||||
|
private String mailDomainName;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
import de.otto.edison.hal.Embedded;
|
import de.otto.edison.hal.Embedded;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
@@ -40,7 +41,10 @@ public class MeDto extends HalRepresentation {
|
|||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
private String mail;
|
private String mail;
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
private String fallbackMail;
|
||||||
private Set<String> groups;
|
private Set<String> groups;
|
||||||
|
|
||||||
MeDto(Links links, Embedded embedded) {
|
MeDto(Links links, Embedded embedded) {
|
||||||
|
|||||||
@@ -24,12 +24,14 @@
|
|||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import de.otto.edison.hal.Embedded;
|
import de.otto.edison.hal.Embedded;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import org.apache.shiro.subject.PrincipalCollection;
|
import org.apache.shiro.subject.PrincipalCollection;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import sonia.scm.group.GroupCollector;
|
import sonia.scm.group.GroupCollector;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.user.UserPermissions;
|
import sonia.scm.user.UserPermissions;
|
||||||
@@ -46,12 +48,14 @@ public class MeDtoFactory extends HalAppenderMapper {
|
|||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
private final UserManager userManager;
|
private final UserManager userManager;
|
||||||
private final GroupCollector groupCollector;
|
private final GroupCollector groupCollector;
|
||||||
|
private final EMail eMail;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager, GroupCollector groupCollector) {
|
public MeDtoFactory(ResourceLinks resourceLinks, UserManager userManager, GroupCollector groupCollector, EMail eMail) {
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
this.userManager = userManager;
|
this.userManager = userManager;
|
||||||
this.groupCollector = groupCollector;
|
this.groupCollector = groupCollector;
|
||||||
|
this.eMail = eMail;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MeDto create() {
|
public MeDto create() {
|
||||||
@@ -61,6 +65,7 @@ public class MeDtoFactory extends HalAppenderMapper {
|
|||||||
MeDto dto = createDto(user);
|
MeDto dto = createDto(user);
|
||||||
mapUserProperties(user, dto);
|
mapUserProperties(user, dto);
|
||||||
mapGroups(user, dto);
|
mapGroups(user, dto);
|
||||||
|
setGeneratedMail(user, dto);
|
||||||
return dto;
|
return dto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,6 +84,12 @@ public class MeDtoFactory extends HalAppenderMapper {
|
|||||||
return subject.getPrincipals();
|
return subject.getPrincipals();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setGeneratedMail(User user, MeDto dto) {
|
||||||
|
if (Strings.isNullOrEmpty(user.getMail())) {
|
||||||
|
dto.setFallbackMail(eMail.getMailOrFallback(user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private MeDto createDto(User user) {
|
private MeDto createDto(User user) {
|
||||||
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
Links.Builder linksBuilder = linkingTo().self(resourceLinks.me().self());
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ public class UserDto extends HalRepresentation {
|
|||||||
private String displayName;
|
private String displayName;
|
||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
private Instant lastModified;
|
private Instant lastModified;
|
||||||
@NotEmpty @Email
|
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||||
|
@Email
|
||||||
private String mail;
|
private String mail;
|
||||||
@Pattern(regexp = ValidationUtil.REGEX_NAME)
|
@Pattern(regexp = ValidationUtil.REGEX_NAME)
|
||||||
private String name;
|
private String name;
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
|||||||
assertFalse(config.isEnabledUserConverter());
|
assertFalse(config.isEnabledUserConverter());
|
||||||
assertEquals("username", config.getNamespaceStrategy());
|
assertEquals("username", config.getNamespaceStrategy());
|
||||||
assertEquals("https://scm-manager.org/login-info", config.getLoginInfoUrl());
|
assertEquals("https://scm-manager.org/login-info", config.getLoginInfoUrl());
|
||||||
|
assertEquals("hitchhiker.mail", config.getMailDomainName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -113,6 +114,7 @@ public class ConfigDtoToScmConfigurationMapperTest {
|
|||||||
configDto.setEnabledXsrfProtection(true);
|
configDto.setEnabledXsrfProtection(true);
|
||||||
configDto.setNamespaceStrategy("username");
|
configDto.setNamespaceStrategy("username");
|
||||||
configDto.setLoginInfoUrl("https://scm-manager.org/login-info");
|
configDto.setLoginInfoUrl("https://scm-manager.org/login-info");
|
||||||
|
configDto.setMailDomainName("hitchhiker.mail");
|
||||||
configDto.setEnabledUserConverter(false);
|
configDto.setEnabledUserConverter(false);
|
||||||
|
|
||||||
return configDto;
|
return configDto;
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ import org.mockito.junit.jupiter.MockitoSettings;
|
|||||||
import org.mockito.quality.Strictness;
|
import org.mockito.quality.Strictness;
|
||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.group.GroupCollector;
|
import sonia.scm.group.GroupCollector;
|
||||||
|
import sonia.scm.user.EMail;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.user.UserPermissions;
|
|
||||||
import sonia.scm.user.UserTestData;
|
import sonia.scm.user.UserTestData;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@@ -65,13 +65,16 @@ class MeDtoFactoryTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private Subject subject;
|
private Subject subject;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EMail eMail;
|
||||||
|
|
||||||
private MeDtoFactory meDtoFactory;
|
private MeDtoFactory meDtoFactory;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUpContext() {
|
void setUpContext() {
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
meDtoFactory = new MeDtoFactory(resourceLinks, userManager, groupCollector);
|
meDtoFactory = new MeDtoFactory(resourceLinks, userManager, groupCollector, eMail);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
@@ -235,4 +238,17 @@ class MeDtoFactoryTest {
|
|||||||
MeDto dto = meDtoFactory.create();
|
MeDto dto = meDtoFactory.create();
|
||||||
assertThat(dto.getLinks().getLinkBy("profile").get().getHref()).isEqualTo("http://hitchhiker.com/users/trillian");
|
assertThat(dto.getLinks().getLinkBy("profile").get().getHref()).isEqualTo("http://hitchhiker.com/users/trillian");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldUserGeneratedMailOnlyWhenUserHasNone() {
|
||||||
|
User user = UserTestData.createTrillian();
|
||||||
|
user.setMail(null);
|
||||||
|
prepareSubject(user);
|
||||||
|
when(eMail.getMailOrFallback(user)).thenReturn("trillian@hitchhiker.local");
|
||||||
|
|
||||||
|
MeDto dto = meDtoFactory.create();
|
||||||
|
|
||||||
|
assertThat(dto.getMail()).isNull();
|
||||||
|
assertThat(dto.getFallbackMail()).isEqualTo("trillian@hitchhiker.local");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,7 @@ public class ScmConfigurationToConfigDtoMapperTest {
|
|||||||
assertEquals("username", dto.getNamespaceStrategy());
|
assertEquals("username", dto.getNamespaceStrategy());
|
||||||
assertEquals("https://scm-manager.org/login-info", dto.getLoginInfoUrl());
|
assertEquals("https://scm-manager.org/login-info", dto.getLoginInfoUrl());
|
||||||
assertEquals("https://www.scm-manager.org/download/rss.xml", dto.getReleaseFeedUrl());
|
assertEquals("https://www.scm-manager.org/download/rss.xml", dto.getReleaseFeedUrl());
|
||||||
|
assertEquals("scm-manager.local", dto.getMailDomainName());
|
||||||
|
|
||||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("self").get().getHref());
|
||||||
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
assertEquals(expectedBaseUri.toString(), dto.getLinks().getLinkBy("update").get().getHref());
|
||||||
|
|||||||
Reference in New Issue
Block a user