From 4210dc0c6d956ff1bad372479f2c24a97e155898 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 20 Jul 2020 15:07:11 +0200 Subject: [PATCH 01/46] implemented public api for verifying changeset signatures --- .../java/sonia/scm/repository/Changeset.java | 33 +++++++++++++ .../java/sonia/scm/repository/Signature.java | 47 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/repository/Signature.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index e2c980db37..5edd01531e 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -32,6 +32,7 @@ import sonia.scm.util.ValidationUtil; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; @@ -85,6 +86,8 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { */ private Collection contributors; + private List signatures; + public Changeset() {} public Changeset(String id, Long date, Person author) @@ -348,4 +351,34 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { this.contributors.addAll(contributors); } } + + /** + * Sets a collection of signatures which belong to this changeset. + * @param signatures collection of signatures + * @since 2.3.0 + */ + public void setSignatures(Collection signatures) { + this.signatures = new ArrayList<>(signatures); + } + + /** + * Returns a immutable list of signatures. + * @return signatures + * @since 2.3.0 + */ + public List getSignatures() { + return Collections.unmodifiableList(signatures); + } + + /** + * Adds a signature to the list of signatures. + * @param signature + * @since 2.3.0 + */ + public void addSignature(Signature signature) { + if (signatures == null) { + signatures = new ArrayList<>(); + } + signatures.add(signature); + } } diff --git a/scm-core/src/main/java/sonia/scm/repository/Signature.java b/scm-core/src/main/java/sonia/scm/repository/Signature.java new file mode 100644 index 0000000000..4cada0e629 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/Signature.java @@ -0,0 +1,47 @@ +/* + * 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; + +import lombok.Value; + +import java.util.Optional; + +/** + * Signature is the output of a signature verification. + * @since 2.3.0 + */ +@Value +public class Signature { + + private final String key; + private final String type; + private final boolean verified; + private final String owner; + + public Optional getOwner() { + return Optional.ofNullable(owner); + } + +} From 1015445ba57300b3981eae235c7d4071ed202949 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 20 Jul 2020 15:07:35 +0200 Subject: [PATCH 02/46] added public api for resolving public gpg keys --- .../java/sonia/scm/security/GPGPublicKey.java | 40 ++++++++++++++++ .../scm/security/GPGPublicKeyResolver.java | 48 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java create mode 100644 scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java diff --git a/scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java b/scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java new file mode 100644 index 0000000000..731b958c09 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java @@ -0,0 +1,40 @@ +/* + * 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.security; + +import lombok.Value; + +/** + * Public gpg key. + * @since 2.3.0 + */ +@Value +public class GPGPublicKey { + + private final String id; + private final String owner; + private final String raw; + +} diff --git a/scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java b/scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java new file mode 100644 index 0000000000..56158ec36a --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java @@ -0,0 +1,48 @@ +/* + * 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.security; + +import java.util.List; +import java.util.Optional; + +/** + * Resolver for public gpg keys. + * @since 2.3.0 + */ +public interface GPGPublicKeyResolver { + + /** + * Resolves the public key by its id. + * @param keyId id of the key + * @return public gpg key or empty optional. + */ + Optional byId(String keyId); + + /** + * Resolves all public gpg keys for the given user. + * @return list of public gpg keys + */ + List byUser(String username); +} From 6e27051ed9987b087d71238979162c06dc642314 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 20 Jul 2020 16:17:18 +0200 Subject: [PATCH 03/46] use child injector pattern for git commands We are using a Google Guice child injector to reduce the amount of injection dependencies for the GitRepositoryServiceResolver and the GitRepositoryServiceProvider. --- scm-core/pom.xml | 6 + .../scm/repository/spi/GitBlameCommand.java | 2 + .../scm/repository/spi/GitBranchCommand.java | 2 + .../repository/spi/GitBranchesCommand.java | 2 + .../scm/repository/spi/GitBrowseCommand.java | 6 + .../scm/repository/spi/GitCatCommand.java | 2 + .../scm/repository/spi/GitContextFactory.java | 48 ++++ .../scm/repository/spi/GitDiffCommand.java | 2 + .../repository/spi/GitDiffResultCommand.java | 2 + .../repository/spi/GitIncomingCommand.java | 2 + .../scm/repository/spi/GitLogCommand.java | 2 + .../scm/repository/spi/GitMergeCommand.java | 7 + .../spi/GitModificationsCommand.java | 4 +- .../scm/repository/spi/GitModifyCommand.java | 7 + .../repository/spi/GitOutgoingCommand.java | 2 + .../scm/repository/spi/GitPullCommand.java | 2 + .../scm/repository/spi/GitPushCommand.java | 5 +- .../spi/GitRepositoryServiceProvider.java | 210 ++++-------------- .../spi/GitRepositoryServiceResolver.java | 32 +-- .../repository/spi/GitDiffCommandTest.java | 1 - .../spi/GitRepositoryServiceProviderTest.java | 74 ++++++ .../spi/GitRepositoryServiceResolverTest.java | 63 ++++++ scm-webapp/pom.xml | 6 - 23 files changed, 290 insertions(+), 199 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceProviderTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceResolverTest.java diff --git a/scm-core/pom.xml b/scm-core/pom.xml index 2ada8e2e76..1cadd49ae7 100644 --- a/scm-core/pom.xml +++ b/scm-core/pom.xml @@ -112,6 +112,12 @@ ${guice.version} + + com.google.inject.extensions + guice-assistedinject + ${guice.version} + + diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java index 5c1f074a90..d99343b350 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBlameCommand.java @@ -41,6 +41,7 @@ import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Person; +import javax.inject.Inject; import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -64,6 +65,7 @@ public class GitBlameCommand extends AbstractGitCommand implements BlameCommand //~--- constructors --------------------------------------------------------- + @Inject public GitBlameCommand(GitContext context) { super(context); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java index 506580d5a9..82a62ef111 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchCommand.java @@ -42,6 +42,7 @@ import sonia.scm.repository.api.HookContext; import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.HookFeature; +import javax.inject.Inject; import java.io.IOException; import java.util.List; import java.util.Set; @@ -56,6 +57,7 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman private final HookContextFactory hookContextFactory; private final ScmEventBus eventBus; + @Inject GitBranchCommand(GitContext context, HookContextFactory hookContextFactory, ScmEventBus eventBus) { super(context); this.hookContextFactory = hookContextFactory; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java index cd7892461b..d026affd8b 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBranchesCommand.java @@ -38,6 +38,7 @@ import sonia.scm.repository.Branch; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; +import javax.inject.Inject; import java.io.IOException; import java.util.List; import java.util.Optional; @@ -53,6 +54,7 @@ public class GitBranchesCommand extends AbstractGitCommand implements BranchesCo private static final Logger LOG = LoggerFactory.getLogger(GitBranchesCommand.class); + @Inject public GitBranchesCommand(GitContext context) { super(context); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index ab5a7d33b4..05792b9707 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -57,6 +57,7 @@ import sonia.scm.store.BlobStore; import sonia.scm.util.Util; import sonia.scm.web.lfs.LfsBlobStoreFactory; +import javax.inject.Inject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; @@ -111,6 +112,11 @@ public class GitBrowseCommand extends AbstractGitCommand private int resultCount = 0; + @Inject + public GitBrowseCommand(GitContext context, LfsBlobStoreFactory lfsBlobStoreFactory, SyncAsyncExecutorProvider executorProvider) { + this(context, lfsBlobStoreFactory, executorProvider.createExecutorWithDefaultTimeout()); + } + public GitBrowseCommand(GitContext context, LfsBlobStoreFactory lfsBlobStoreFactory, SyncAsyncExecutor executor) { super(context); this.lfsBlobStoreFactory = lfsBlobStoreFactory; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java index 4629e4bce2..8c2e3b44d5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java @@ -44,6 +44,7 @@ import sonia.scm.util.IOUtil; import sonia.scm.util.Util; import sonia.scm.web.lfs.LfsBlobStoreFactory; +import javax.inject.Inject; import java.io.Closeable; import java.io.FilterInputStream; import java.io.IOException; @@ -61,6 +62,7 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { private final LfsBlobStoreFactory lfsBlobStoreFactory; + @Inject public GitCatCommand(GitContext context, LfsBlobStoreFactory lfsBlobStoreFactory) { super(context); this.lfsBlobStoreFactory = lfsBlobStoreFactory; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java new file mode 100644 index 0000000000..04bb37bed0 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitContextFactory.java @@ -0,0 +1,48 @@ +/* + * 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.spi; + +import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.Repository; + +import javax.inject.Inject; + +class GitContextFactory { + + private final GitRepositoryHandler handler; + private final GitRepositoryConfigStoreProvider storeProvider; + + @Inject + GitContextFactory(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider) { + this.handler = handler; + this.storeProvider = storeProvider; + } + + GitContext create(Repository repository) { + return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider); + } + +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index adb7a7bd0e..c0ed1a53bc 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -29,6 +29,7 @@ import org.eclipse.jgit.diff.DiffFormatter; import org.eclipse.jgit.util.QuotedString; import sonia.scm.repository.api.DiffCommandBuilder; +import javax.inject.Inject; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -41,6 +42,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; */ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { + @Inject GitDiffCommand(GitContext context) { super(context); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java index fed865c576..e55d8badae 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffResultCommand.java @@ -32,6 +32,7 @@ import sonia.scm.repository.api.DiffFile; import sonia.scm.repository.api.DiffResult; import sonia.scm.repository.api.Hunk; +import javax.inject.Inject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Iterator; @@ -39,6 +40,7 @@ import java.util.stream.Collectors; public class GitDiffResultCommand extends AbstractGitCommand implements DiffResultCommand { + @Inject GitDiffResultCommand(GitContext context) { super(context); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java index f6e818bcdb..638983b6f1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java @@ -31,6 +31,7 @@ import org.eclipse.jgit.lib.ObjectId; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitRepositoryHandler; +import javax.inject.Inject; import java.io.IOException; //~--- JDK imports ------------------------------------------------------------ @@ -49,6 +50,7 @@ public class GitIncomingCommand extends AbstractGitIncomingOutgoingCommand * @param handler * @param context */ + @Inject GitIncomingCommand(GitRepositoryHandler handler, GitContext context) { super(handler, context); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index da00ba451b..b1205de975 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -47,6 +47,7 @@ import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.util.IOUtil; +import javax.inject.Inject; import java.io.IOException; import java.util.Collections; import java.util.Iterator; @@ -80,6 +81,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand * @param context * */ + @Inject GitLogCommand(GitContext context) { super(context); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 9f362edeb6..bd4e6b26b5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -36,6 +36,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.ResolveMerger; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.filter.PathFilter; +import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitWorkingCopyFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.api.MergeCommandResult; @@ -43,6 +44,7 @@ import sonia.scm.repository.api.MergeDryRunCommandResult; import sonia.scm.repository.api.MergeStrategy; import sonia.scm.repository.api.MergeStrategyNotSupportedException; +import javax.inject.Inject; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Set; @@ -61,6 +63,11 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand MergeStrategy.SQUASH ); + @Inject + GitMergeCommand(GitContext context, GitRepositoryHandler handler) { + this(context, handler.getWorkingCopyFactory()); + } + GitMergeCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory) { super(context); this.workingCopyFactory = workingCopyFactory; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java index 918d276148..e907081f2c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModificationsCommand.java @@ -41,6 +41,7 @@ import sonia.scm.repository.Modified; import sonia.scm.repository.Removed; import sonia.scm.repository.Renamed; +import javax.inject.Inject; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; @@ -53,7 +54,8 @@ import static sonia.scm.ContextEntry.ContextBuilder.entity; @Slf4j public class GitModificationsCommand extends AbstractGitCommand implements ModificationsCommand { - protected GitModificationsCommand(GitContext context) { + @Inject + GitModificationsCommand(GitContext context) { super(context); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index 9e0f5449d3..7f32f55e52 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -34,11 +34,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.ConcurrentModificationException; import sonia.scm.NoChangesMadeException; +import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitWorkingCopyFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.web.lfs.LfsBlobStoreFactory; +import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.nio.file.Path; @@ -53,6 +55,11 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman private final GitWorkingCopyFactory workingCopyFactory; private final LfsBlobStoreFactory lfsBlobStoreFactory; + @Inject + GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory) { + this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory); + } + GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory) { super(context); this.workingCopyFactory = workingCopyFactory; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java index 03acf9e914..bfb319df10 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java @@ -31,6 +31,7 @@ import org.eclipse.jgit.lib.ObjectId; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitRepositoryHandler; +import javax.inject.Inject; import java.io.IOException; //~--- JDK imports ------------------------------------------------------------ @@ -49,6 +50,7 @@ public class GitOutgoingCommand extends AbstractGitIncomingOutgoingCommand * @param handler * @param context */ + @Inject GitOutgoingCommand(GitRepositoryHandler handler, GitContext context) { super(handler, context); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java index 53b7a59916..422391fd19 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPullCommand.java @@ -44,6 +44,7 @@ import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.repository.api.PullResponse; +import javax.inject.Inject; import java.io.File; import java.io.IOException; import java.net.URL; @@ -73,6 +74,7 @@ public class GitPullCommand extends AbstractGitPushOrPullCommand * @param handler * @param context */ + @Inject public GitPullCommand(GitRepositoryHandler handler, GitContext context) { super(handler, context); diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java index fd874524f4..ddbfb6a8e1 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitPushCommand.java @@ -31,6 +31,7 @@ import org.slf4j.LoggerFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.api.PushResponse; +import javax.inject.Inject; import java.io.IOException; //~--- JDK imports ------------------------------------------------------------ @@ -55,8 +56,8 @@ public class GitPushCommand extends AbstractGitPushOrPullCommand * @param handler * @param context */ - public GitPushCommand(GitRepositoryHandler handler, GitContext context) - { + @Inject + public GitPushCommand(GitRepositoryHandler handler, GitContext context) { super(handler, context); this.handler = handler; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 862631c32c..75b0f97b3f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -25,21 +25,14 @@ package sonia.scm.repository.spi; import com.google.common.collect.ImmutableSet; -import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; -import sonia.scm.event.ScmEventBus; +import com.google.inject.AbstractModule; +import com.google.inject.Injector; import sonia.scm.repository.Feature; -import sonia.scm.repository.GitRepositoryHandler; -import sonia.scm.repository.Repository; import sonia.scm.repository.api.Command; -import sonia.scm.repository.api.HookContextFactory; -import sonia.scm.web.lfs.LfsBlobStoreFactory; -import java.io.IOException; import java.util.EnumSet; import java.util.Set; -//~--- JDK imports ------------------------------------------------------------ - /** * * @author Sebastian Sdorra @@ -47,8 +40,6 @@ import java.util.Set; public class GitRepositoryServiceProvider extends RepositoryServiceProvider { - /** Field description */ - //J- public static final Set COMMANDS = ImmutableSet.of( Command.BLAME, Command.BROWSE, @@ -66,105 +57,51 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider Command.MERGE, Command.MODIFY ); + protected static final Set FEATURES = EnumSet.of(Feature.INCOMING_REVISION); - //J+ + + private final GitContext context; + private final Injector commandInjector; //~--- constructors --------------------------------------------------------- - public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus, SyncAsyncExecutorProvider executorProvider) { - this.handler = handler; - this.lfsBlobStoreFactory = lfsBlobStoreFactory; - this.hookContextFactory = hookContextFactory; - this.eventBus = eventBus; - this.executorProvider = executorProvider; - this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider); + GitRepositoryServiceProvider(Injector injector, GitContext context) { + this.context = context; + commandInjector = injector.createChildInjector(new AbstractModule() { + @Override + protected void configure() { + bind(GitContext.class).toInstance(context); + } + }); } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @throws IOException - */ @Override - public void close() throws IOException - { - context.close(); - } - - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ - @Override - public BlameCommand getBlameCommand() - { + public BlameCommand getBlameCommand() { return new GitBlameCommand(context); } - /** - * Method description - * - * - * @return - */ @Override - public BranchesCommand getBranchesCommand() - { + public BranchesCommand getBranchesCommand() { return new GitBranchesCommand(context); } - /** - * Method description - * - * - * @return - */ @Override - public BranchCommand getBranchCommand() - { - return new GitBranchCommand(context, hookContextFactory, eventBus); + public BranchCommand getBranchCommand() { + return commandInjector.getInstance(GitBranchCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public BrowseCommand getBrowseCommand() - { - return new GitBrowseCommand(context, lfsBlobStoreFactory, executorProvider.createExecutorWithDefaultTimeout()); + public BrowseCommand getBrowseCommand() { + return commandInjector.getInstance(GitBrowseCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public CatCommand getCatCommand() - { - return new GitCatCommand(context, lfsBlobStoreFactory); + public CatCommand getCatCommand() { + return commandInjector.getInstance(GitCatCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public DiffCommand getDiffCommand() - { + public DiffCommand getDiffCommand() { return new GitDiffCommand(context); } @@ -173,27 +110,13 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider return new GitDiffResultCommand(context); } - /** - * Method description - * - * - * @return - */ @Override - public IncomingCommand getIncomingCommand() - { - return new GitIncomingCommand(handler, context); + public IncomingCommand getIncomingCommand() { + return commandInjector.getInstance(GitIncomingCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public LogCommand getLogCommand() - { + public LogCommand getLogCommand() { return new GitLogCommand(context); } @@ -202,93 +125,48 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider return new GitModificationsCommand(context); } - /** - * Method description - * - * - * @return - */ @Override - public OutgoingCommand getOutgoingCommand() - { - return new GitOutgoingCommand(handler, context); + public OutgoingCommand getOutgoingCommand() { + return commandInjector.getInstance(GitOutgoingCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public PullCommand getPullCommand() - { - return new GitPullCommand(handler, context); + public PullCommand getPullCommand() { + return commandInjector.getInstance(GitPullCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public PushCommand getPushCommand() - { - return new GitPushCommand(handler, context); + public PushCommand getPushCommand() { + return commandInjector.getInstance(GitPushCommand.class); } - /** - * Method description - * - * - * @return - */ @Override - public Set getSupportedCommands() - { - return COMMANDS; - } - - /** - * Method description - * - * - * @return - */ - @Override - public TagsCommand getTagsCommand() - { + public TagsCommand getTagsCommand() { return new GitTagsCommand(context); } @Override public MergeCommand getMergeCommand() { - return new GitMergeCommand(context, handler.getWorkingCopyFactory()); + return commandInjector.getInstance(GitMergeCommand.class); } @Override public ModifyCommand getModifyCommand() { - return new GitModifyCommand(context, handler.getWorkingCopyFactory(), lfsBlobStoreFactory); + return commandInjector.getInstance(GitModifyCommand.class); + } + + @Override + public Set getSupportedCommands() { + return COMMANDS; } @Override public Set getSupportedFeatures() { return FEATURES; } -//~--- fields --------------------------------------------------------------- - /** Field description */ - private final GitContext context; - - /** Field description */ - private final GitRepositoryHandler handler; - - private final LfsBlobStoreFactory lfsBlobStoreFactory; - - private final HookContextFactory hookContextFactory; - - private final ScmEventBus eventBus; - - private final SyncAsyncExecutorProvider executorProvider; + @Override + public void close() { + context.close(); + } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java index 8ffda05ad3..7ff06dd140 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java @@ -21,19 +21,16 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.google.inject.Inject; -import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; -import sonia.scm.event.ScmEventBus; +import com.google.inject.Injector; import sonia.scm.plugin.Extension; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.Repository; -import sonia.scm.repository.api.HookContextFactory; -import sonia.scm.web.lfs.LfsBlobStoreFactory; /** * @@ -42,31 +39,20 @@ import sonia.scm.web.lfs.LfsBlobStoreFactory; @Extension public class GitRepositoryServiceResolver implements RepositoryServiceResolver { - private final GitRepositoryHandler handler; - private final GitRepositoryConfigStoreProvider storeProvider; - private final LfsBlobStoreFactory lfsBlobStoreFactory; - private final HookContextFactory hookContextFactory; - private final ScmEventBus eventBus; - private final SyncAsyncExecutorProvider executorProvider; + private final Injector injector; + private final GitContextFactory contextFactory; @Inject - public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus, SyncAsyncExecutorProvider executorProvider) { - this.handler = handler; - this.storeProvider = storeProvider; - this.lfsBlobStoreFactory = lfsBlobStoreFactory; - this.hookContextFactory = hookContextFactory; - this.eventBus = eventBus; - this.executorProvider = executorProvider; + public GitRepositoryServiceResolver(Injector injector, GitContextFactory contextFactory) { + this.injector = injector; + this.contextFactory = contextFactory; } @Override public GitRepositoryServiceProvider resolve(Repository repository) { - GitRepositoryServiceProvider provider = null; - if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) { - provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory, hookContextFactory, eventBus, executorProvider); + return new GitRepositoryServiceProvider(injector, contextFactory.create(repository)); } - - return provider; + return null; } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java index 7a9e9f660e..fea4a33f15 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java @@ -24,7 +24,6 @@ package sonia.scm.repository.spi; -import org.assertj.core.api.Assertions; import org.junit.Test; import java.io.ByteArrayOutputStream; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceProviderTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceProviderTest.java new file mode 100644 index 0000000000..aac6eaef49 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceProviderTest.java @@ -0,0 +1,74 @@ +/* + * 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.spi; + +import com.google.inject.AbstractModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.GitRepositoryHandler; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; + +@ExtendWith(MockitoExtension.class) +class GitRepositoryServiceProviderTest { + + @Mock + private GitRepositoryHandler handler; + + @Mock + private GitContext context; + + @Test + void shouldCreatePushCommand() { + GitRepositoryServiceProvider provider = createProvider(); + PushCommand pushCommand = provider.getPushCommand(); + assertThat(pushCommand).isNotNull().isInstanceOf(GitPushCommand.class); + } + + @Test + void shouldDelegateCloseToContext() { + createProvider().close(); + verify(context).close(); + } + + private GitRepositoryServiceProvider createProvider() { + return new GitRepositoryServiceProvider(createParentInjector(), context); + } + + private Injector createParentInjector() { + return Guice.createInjector(new AbstractModule() { + @Override + protected void configure() { + bind(GitRepositoryHandler.class).toInstance(handler); + } + }); + } + +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceResolverTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceResolverTest.java new file mode 100644 index 0000000000..6c2de8b7b6 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitRepositoryServiceResolverTest.java @@ -0,0 +1,63 @@ +/* + * 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.spi; + +import com.google.inject.Injector; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.repository.RepositoryTestData; + +import static org.assertj.core.api.Assertions.assertThat; + +@ExtendWith(MockitoExtension.class) +class GitRepositoryServiceResolverTest { + + @Mock + private Injector injector; + + @Mock + private GitContextFactory contextFactory; + + @InjectMocks + private GitRepositoryServiceResolver resolver; + + @Test + void shouldCreateRepositoryServiceProvider() { + GitRepositoryServiceProvider provider = resolver.resolve(RepositoryTestData.createHeartOfGold("git")); + assertThat(provider).isNotNull(); + } + + @ParameterizedTest + @ValueSource(strings = { "hg","svn", "unknown"}) + void shouldReturnNullForNonGitRepositories(String type) { + GitRepositoryServiceProvider provider = resolver.resolve(RepositoryTestData.createHeartOfGold(type)); + assertThat(provider).isNull(); + } +} diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 4c178bd37c..5daec406b4 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -222,12 +222,6 @@ ${guice.version} - - com.google.inject.extensions - guice-assistedinject - ${guice.version} - - From 7cb349242c4476ac321886bcf5d67fbccc38ccf3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Tue, 21 Jul 2020 07:05:26 +0200 Subject: [PATCH 04/46] introduces GitChangesetConverterFactory This change introduces a GitChangesetConverterFactory to allow injections for the GitChangesetConverter. --- .../jgit/transport/ScmTransportProtocol.java | 182 ++++-------------- .../git/BaseReceivePackFactory.java | 7 +- .../git/ScmReceivePackFactory.java | 7 +- .../scm/repository/GitChangesetConverter.java | 4 +- .../GitChangesetConverterFactory.java | 40 ++++ .../repository/GitHookChangesetCollector.java | 14 +- .../AbstractGitIncomingOutgoingCommand.java | 16 +- .../spi/GitHookChangesetProvider.java | 58 ++---- .../spi/GitHookContextProvider.java | 9 +- .../repository/spi/GitIncomingCommand.java | 15 +- .../scm/repository/spi/GitLogCommand.java | 9 +- .../repository/spi/GitOutgoingCommand.java | 14 +- .../spi/GitRepositoryServiceProvider.java | 2 +- .../java/sonia/scm/web/GitReceiveHook.java | 11 +- .../sonia/scm/web/GitReceivePackFactory.java | 7 +- .../git/BaseReceivePackFactoryTest.java | 5 +- .../client/spi/GitCommitCommand.java | 6 +- .../spi/AbstractRemoteCommandTestBase.java | 21 +- .../spi/BindTransportProtocolRule.java | 5 +- .../spi/GitIncomingCommandTest.java | 16 +- .../spi/GitLogCommandAncestorTest.java | 6 +- .../scm/repository/spi/GitLogCommandTest.java | 6 +- .../spi/GitOutgoingCommandTest.java | 7 +- .../spi/SimpleGitWorkingCopyFactoryTest.java | 3 +- 24 files changed, 179 insertions(+), 291 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/org/eclipse/jgit/transport/ScmTransportProtocol.java b/scm-plugins/scm-git-plugin/src/main/java/org/eclipse/jgit/transport/ScmTransportProtocol.java index 6c5efe722c..61c3013f36 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/org/eclipse/jgit/transport/ScmTransportProtocol.java +++ b/scm-plugins/scm-git-plugin/src/main/java/org/eclipse/jgit/transport/ScmTransportProtocol.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package org.eclipse.jgit.transport; //~--- non-JDK imports -------------------------------------------------------- @@ -29,200 +29,106 @@ package org.eclipse.jgit.transport; import com.google.common.collect.ImmutableSet; import com.google.inject.Inject; import com.google.inject.Provider; - import org.eclipse.jgit.errors.NoRemoteRepositoryException; -import org.eclipse.jgit.errors.NotSupportedException; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryCache; - +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.web.CollectingPackParserListener; import sonia.scm.web.GitReceiveHook; -//~--- JDK imports ------------------------------------------------------------ - import java.io.File; - import java.util.Set; +//~--- JDK imports ------------------------------------------------------------ + /** - * * @author Sebastian Sdorra */ -public class ScmTransportProtocol extends TransportProtocol -{ +public class ScmTransportProtocol extends TransportProtocol { - /** Field description */ public static final String NAME = "scm"; - - /** Field description */ private static final Set SCHEMES = ImmutableSet.of(NAME); - //~--- constructors --------------------------------------------------------- + private Provider converterFactory; + private Provider hookEventFacadeProvider; + private Provider repositoryHandlerProvider; - /** - * Constructs ... - * - */ - public ScmTransportProtocol() {} + public ScmTransportProtocol() { + } - /** - * Constructs ... - * - * - * - * @param hookEventFacadeProvider - * - * @param repositoryHandlerProvider - */ @Inject public ScmTransportProtocol( + Provider converterFactory, Provider hookEventFacadeProvider, - Provider repositoryHandlerProvider) - { + Provider repositoryHandlerProvider) { + this.converterFactory = converterFactory; this.hookEventFacadeProvider = hookEventFacadeProvider; this.repositoryHandlerProvider = repositoryHandlerProvider; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param uri - * @param local - * @param remoteName - * - * @return - */ @Override - public boolean canHandle(URIish uri, Repository local, String remoteName) - { - if ((uri.getPath() == null) || (uri.getPort() > 0) - || (uri.getUser() != null) || (uri.getPass() != null) - || (uri.getHost() != null) - || ((uri.getScheme() != null) &&!getSchemes().contains(uri.getScheme()))) - { - return false; - } - - return true; + public boolean canHandle(URIish uri, Repository local, String remoteName) { + return (uri.getPath() != null) && (uri.getPort() <= 0) + && (uri.getUser() == null) && (uri.getPass() == null) + && (uri.getHost() == null) + && ((uri.getScheme() == null) || getSchemes().contains(uri.getScheme())); } - /** - * Method description - * - * - * @param uri - * @param local - * @param remoteName - * - * @return - * - * @throws NotSupportedException - * @throws TransportException - */ @Override - public Transport open(URIish uri, Repository local, String remoteName) - throws TransportException - { + public Transport open(URIish uri, Repository local, String remoteName) throws TransportException { File localDirectory = local.getDirectory(); File path = local.getFS().resolve(localDirectory, uri.getPath()); File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS()); - if (gitDir == null) - { + if (gitDir == null) { throw new NoRemoteRepositoryException(uri, JGitText.get().notFound); } - //J- return new TransportLocalWithHooks( + converterFactory.get(), hookEventFacadeProvider.get(), repositoryHandlerProvider.get(), local, uri, gitDir ); - //J+ } - //~--- get methods ---------------------------------------------------------- - - /** - * Method description - * - * - * @return - */ @Override - public String getName() - { + public String getName() { return NAME; } - /** - * Method description - * - * - * @return - */ @Override - public Set getSchemes() - { + public Set getSchemes() { return SCHEMES; } - //~--- inner classes -------------------------------------------------------- + private static class TransportLocalWithHooks extends TransportLocal { - /** - * Class description - * - * - * @version Enter version here..., 13/05/19 - * @author Enter your name here... - */ - private static class TransportLocalWithHooks extends TransportLocal - { + private final GitChangesetConverterFactory converterFactory; + private final GitRepositoryHandler handler; + private final HookEventFacade hookEventFacade; - /** - * Constructs ... - * - * - * - * @param hookEventFacade - * @param handler - * @param local - * @param uri - * @param gitDir - */ - public TransportLocalWithHooks(HookEventFacade hookEventFacade, - GitRepositoryHandler handler, Repository local, URIish uri, File gitDir) - { + public TransportLocalWithHooks( + GitChangesetConverterFactory converterFactory, + HookEventFacade hookEventFacade, + GitRepositoryHandler handler, + Repository local, URIish uri, File gitDir) { super(local, uri, gitDir); + this.converterFactory = converterFactory; this.hookEventFacade = hookEventFacade; this.handler = handler; } - //~--- methods ------------------------------------------------------------ - - /** - * Method description - * - * - * @param dst - * - * @return - */ @Override - ReceivePack createReceivePack(Repository dst) - { + ReceivePack createReceivePack(Repository dst) { ReceivePack pack = new ReceivePack(dst); - if ((hookEventFacade != null) && (handler != null)) - { - GitReceiveHook hook = new GitReceiveHook(hookEventFacade, handler); + if ((hookEventFacade != null) && (handler != null) && (converterFactory != null)) { + GitReceiveHook hook = new GitReceiveHook(converterFactory, hookEventFacade, handler); pack.setPreReceiveHook(hook); pack.setPostReceiveHook(hook); @@ -232,22 +138,6 @@ public class ScmTransportProtocol extends TransportProtocol return pack; } - - //~--- fields ------------------------------------------------------------- - - /** Field description */ - private GitRepositoryHandler handler; - - /** Field description */ - private HookEventFacade hookEventFacade; } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private Provider hookEventFacadeProvider; - - /** Field description */ - private Provider repositoryHandlerProvider; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/BaseReceivePackFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/BaseReceivePackFactory.java index 9b18991d1a..028e91009d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/BaseReceivePackFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/BaseReceivePackFactory.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.protocolcommand.git; import org.eclipse.jgit.lib.Repository; @@ -29,6 +29,7 @@ import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.spi.HookEventFacade; import sonia.scm.web.CollectingPackParserListener; @@ -39,9 +40,9 @@ public abstract class BaseReceivePackFactory implements ReceivePackFactory private final GitRepositoryHandler handler; private final GitReceiveHook hook; - protected BaseReceivePackFactory(GitRepositoryHandler handler, HookEventFacade hookEventFacade) { + protected BaseReceivePackFactory(GitChangesetConverterFactory converterFactory, GitRepositoryHandler handler, HookEventFacade hookEventFacade) { this.handler = handler; - this.hook = new GitReceiveHook(hookEventFacade, handler); + this.hook = new GitReceiveHook(converterFactory, hookEventFacade, handler); } @Override diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/ScmReceivePackFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/ScmReceivePackFactory.java index f15ccfcf99..a999a57d4d 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/ScmReceivePackFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/protocolcommand/git/ScmReceivePackFactory.java @@ -21,21 +21,22 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.protocolcommand.git; import com.google.inject.Inject; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.transport.ReceivePack; import sonia.scm.protocolcommand.RepositoryContext; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.spi.HookEventFacade; public class ScmReceivePackFactory extends BaseReceivePackFactory { @Inject - public ScmReceivePackFactory(GitRepositoryHandler handler, HookEventFacade hookEventFacade) { - super(handler, hookEventFacade); + public ScmReceivePackFactory(GitChangesetConverterFactory converterFactory, GitRepositoryHandler handler, HookEventFacade hookEventFacade) { + super(converterFactory, handler, hookEventFacade); } @Override diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java index 0f08f54aaa..3b8e6dadad 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java @@ -67,7 +67,7 @@ public class GitChangesetConverter implements Closeable * * @param repository */ - public GitChangesetConverter(org.eclipse.jgit.lib.Repository repository) + GitChangesetConverter(org.eclipse.jgit.lib.Repository repository) { this(repository, null); } @@ -79,7 +79,7 @@ public class GitChangesetConverter implements Closeable * @param repository * @param revWalk */ - public GitChangesetConverter(org.eclipse.jgit.lib.Repository repository, + GitChangesetConverter(org.eclipse.jgit.lib.Repository repository, RevWalk revWalk) { this.repository = repository; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java new file mode 100644 index 0000000000..c5eb6e312a --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java @@ -0,0 +1,40 @@ +/* + * 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; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.revwalk.RevWalk; + +public class GitChangesetConverterFactory { + + public GitChangesetConverter create(Repository repository) { + return new GitChangesetConverter(repository); + } + + public GitChangesetConverter create(Repository repository, RevWalk revWalk) { + return new GitChangesetConverter(repository, revWalk); + } + +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java index 3071f590bc..5d7de5ce27 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitHookChangesetCollector.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- @@ -72,9 +72,10 @@ public class GitHookChangesetCollector * @param rpack * @param receiveCommands */ - public GitHookChangesetCollector(ReceivePack rpack, + public GitHookChangesetCollector(GitChangesetConverterFactory converterFactory, ReceivePack rpack, List receiveCommands) { + this.converterFactory = converterFactory; this.rpack = rpack; this.receiveCommands = receiveCommands; this.listener = CollectingPackParserListener.get(rpack); @@ -100,14 +101,14 @@ public class GitHookChangesetCollector try { walk = rpack.getRevWalk(); - converter = new GitChangesetConverter(repository, walk); + converter = converterFactory.create(repository, walk); for (ReceiveCommand rc : receiveCommands) { String ref = rc.getRefName(); - + logger.trace("handle receive command, type={}, ref={}, result={}", rc.getType(), ref, rc.getResult()); - + if (rc.getType() == ReceiveCommand.Type.DELETE) { logger.debug("skip delete of ref {}", ref); @@ -130,7 +131,7 @@ public class GitHookChangesetCollector builder.append(rc.getType()).append(", ref="); builder.append(rc.getRefName()).append(", result="); builder.append(rc.getResult()); - + logger.error(builder.toString(), ex); } } @@ -222,5 +223,6 @@ public class GitHookChangesetCollector private final List receiveCommands; + private final GitChangesetConverterFactory converterFactory; private final ReceivePack rpack; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java index 1b03a5a55e..94b1451d68 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitIncomingOutgoingCommand.java @@ -36,6 +36,7 @@ import org.eclipse.jgit.revwalk.RevWalk; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; @@ -58,18 +59,10 @@ public abstract class AbstractGitIncomingOutgoingCommand /** Field description */ private static final String REMOTE_REF_PREFIX = "refs/remote/scm/%s/"; - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * @param handler - * @param context - */ - AbstractGitIncomingOutgoingCommand(GitRepositoryHandler handler, GitContext context) - { + AbstractGitIncomingOutgoingCommand(GitContext context, GitRepositoryHandler handler, GitChangesetConverterFactory converterFactory) { super(context); this.handler = handler; + this.converterFactory = converterFactory; } //~--- methods -------------------------------------------------------------- @@ -132,7 +125,7 @@ public abstract class AbstractGitIncomingOutgoingCommand try { walk = new RevWalk(git.getRepository()); - converter = new GitChangesetConverter(git.getRepository(), walk); + converter = converterFactory.create(git.getRepository(), walk); org.eclipse.jgit.api.LogCommand log = git.log(); @@ -203,4 +196,5 @@ public abstract class AbstractGitIncomingOutgoingCommand /** Field description */ private GitRepositoryHandler handler; + private final GitChangesetConverterFactory converterFactory; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java index 3280e2c5c4..1dbe652371 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookChangesetProvider.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -29,6 +29,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitHookChangesetCollector; //~--- JDK imports ------------------------------------------------------------ @@ -39,56 +40,27 @@ import java.util.List; * * @author Sebastian Sdorra */ -public class GitHookChangesetProvider implements HookChangesetProvider -{ +public class GitHookChangesetProvider implements HookChangesetProvider { - /** - * Constructs ... - * - * - * @param receivePack - * @param receiveCommands - */ - public GitHookChangesetProvider(ReceivePack receivePack, - List receiveCommands) - { + private final GitChangesetConverterFactory converterFactory; + private final ReceivePack receivePack; + private final List receiveCommands; + + private HookChangesetResponse response; + + public GitHookChangesetProvider(GitChangesetConverterFactory converterFactory, ReceivePack receivePack, + List receiveCommands) { + this.converterFactory = converterFactory; this.receivePack = receivePack; this.receiveCommands = receiveCommands; } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - * - * @param request - * - * @return - */ @Override - public synchronized HookChangesetResponse handleRequest( - HookChangesetRequest request) - { - if (response == null) - { - GitHookChangesetCollector collector = - new GitHookChangesetCollector(receivePack, receiveCommands); - + public synchronized HookChangesetResponse handleRequest(HookChangesetRequest request) { + if (response == null) { + GitHookChangesetCollector collector = new GitHookChangesetCollector(converterFactory, receivePack, receiveCommands); response = new HookChangesetResponse(collector.collectChangesets()); } - return response; } - - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private List receiveCommands; - - /** Field description */ - private ReceivePack receivePack; - - /** Field description */ - private HookChangesetResponse response; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java index 8e642a18ce..95843970e4 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitHookContextProvider.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -29,6 +29,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.api.GitHookBranchProvider; import sonia.scm.repository.api.GitHookMessageProvider; import sonia.scm.repository.api.HookBranchProvider; @@ -63,12 +64,12 @@ public class GitHookContextProvider extends HookContextProvider * @param receivePack git receive pack * @param receiveCommands received commands */ - public GitHookContextProvider(ReceivePack receivePack, - List receiveCommands) + public GitHookContextProvider(GitChangesetConverterFactory converterFactory, ReceivePack receivePack, + List receiveCommands) { this.receivePack = receivePack; this.receiveCommands = receiveCommands; - this.changesetProvider = new GitHookChangesetProvider(receivePack, + this.changesetProvider = new GitHookChangesetProvider(converterFactory, receivePack, receiveCommands); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java index 638983b6f1..205d9e9e3f 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java @@ -29,6 +29,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.api.LogCommand; import org.eclipse.jgit.lib.ObjectId; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import javax.inject.Inject; @@ -41,19 +42,11 @@ import java.io.IOException; * @author Sebastian Sdorra */ public class GitIncomingCommand extends AbstractGitIncomingOutgoingCommand - implements IncomingCommand -{ + implements IncomingCommand { - /** - * Constructs ... - * - * @param handler - * @param context - */ @Inject - GitIncomingCommand(GitRepositoryHandler handler, GitContext context) - { - super(handler, context); + GitIncomingCommand(GitContext context, GitRepositoryHandler handler, GitChangesetConverterFactory converterFactory) { + super(context, handler, converterFactory); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java index b1205de975..bd50d0fb07 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitLogCommand.java @@ -43,6 +43,7 @@ import sonia.scm.NotFoundException; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitUtil; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.util.IOUtil; @@ -71,6 +72,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand private static final Logger logger = LoggerFactory.getLogger(GitLogCommand.class); public static final String REVISION = "Revision"; + private final GitChangesetConverterFactory converterFactory; //~--- constructors --------------------------------------------------------- @@ -82,9 +84,10 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand * */ @Inject - GitLogCommand(GitContext context) + GitLogCommand(GitContext context, GitChangesetConverterFactory converterFactory) { super(context); + this.converterFactory = converterFactory; } //~--- get methods ---------------------------------------------------------- @@ -122,7 +125,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand if (commit != null) { - converter = new GitChangesetConverter(gr, revWalk); + converter = converterFactory.create(gr, revWalk); if (isBranchRequested(request)) { String branch = request.getBranch(); @@ -233,7 +236,7 @@ public class GitLogCommand extends AbstractGitCommand implements LogCommand revWalk = new RevWalk(repository); - converter = new GitChangesetConverter(repository, revWalk); + converter = converterFactory.create(repository, revWalk); if (!Strings.isNullOrEmpty(request.getPath())) { revWalk.setTreeFilter( diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java index bfb319df10..30192d4297 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitOutgoingCommand.java @@ -29,6 +29,7 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.api.LogCommand; import org.eclipse.jgit.lib.ObjectId; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import javax.inject.Inject; @@ -41,19 +42,12 @@ import java.io.IOException; * @author Sebastian Sdorra */ public class GitOutgoingCommand extends AbstractGitIncomingOutgoingCommand - implements OutgoingCommand -{ + implements OutgoingCommand { - /** - * Constructs ... - * - * @param handler - * @param context - */ @Inject - GitOutgoingCommand(GitRepositoryHandler handler, GitContext context) + GitOutgoingCommand(GitContext context, GitRepositoryHandler handler, GitChangesetConverterFactory converterFactory) { - super(handler, context); + super(context, handler, converterFactory); } //~--- get methods ---------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 75b0f97b3f..fae69a47cf 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -117,7 +117,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider @Override public LogCommand getLogCommand() { - return new GitLogCommand(context); + return commandInjector.getInstance(GitLogCommand.class); } @Override diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java index c7b323224c..4c127ca9af 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceiveHook.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- @@ -34,6 +34,7 @@ import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.transport.ReceivePack; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.RepositoryHookType; import sonia.scm.repository.spi.GitHookContextProvider; @@ -66,9 +67,10 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook * @param hookEventFacade * @param handler */ - public GitReceiveHook(HookEventFacade hookEventFacade, - GitRepositoryHandler handler) + public GitReceiveHook(GitChangesetConverterFactory converterFactory, HookEventFacade hookEventFacade, + GitRepositoryHandler handler) { + this.converterFactory = converterFactory; this.hookEventFacade = hookEventFacade; this.handler = handler; } @@ -122,7 +124,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook logger.trace("resolved repository to {}", repositoryId); - GitHookContextProvider context = new GitHookContextProvider(rpack, + GitHookContextProvider context = new GitHookContextProvider(converterFactory, rpack, receiveCommands); hookEventFacade.handle(repositoryId).fireHookEvent(type, context); @@ -188,6 +190,7 @@ public class GitReceiveHook implements PreReceiveHook, PostReceiveHook /** Field description */ private GitRepositoryHandler handler; + private final GitChangesetConverterFactory converterFactory; /** Field description */ private HookEventFacade hookEventFacade; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceivePackFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceivePackFactory.java index b4b43ea5b5..dc18189da9 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceivePackFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/web/GitReceivePackFactory.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.web; //~--- non-JDK imports -------------------------------------------------------- @@ -34,6 +34,7 @@ import org.eclipse.jgit.transport.resolver.ReceivePackFactory; import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; import sonia.scm.protocolcommand.git.BaseReceivePackFactory; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.spi.HookEventFacade; @@ -53,8 +54,8 @@ public class GitReceivePackFactory extends BaseReceivePackFactory wrapped; @Inject - public GitReceivePackFactory(GitRepositoryHandler handler, HookEventFacade hookEventFacade) { - super(handler, hookEventFacade); + public GitReceivePackFactory(GitChangesetConverterFactory converterFactory, GitRepositoryHandler handler, HookEventFacade hookEventFacade) { + super(converterFactory, handler, hookEventFacade); this.wrapped = new DefaultReceivePackFactory(); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java index 8fa2ecddaa..d6de6a0780 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.protocolcommand.git; import org.eclipse.jgit.api.Git; @@ -38,6 +38,7 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.web.CollectingPackParserListener; @@ -82,7 +83,7 @@ public class BaseReceivePackFactoryTest { ReceivePack receivePack = new ReceivePack(repository); when(wrappedReceivePackFactory.create(request, repository)).thenReturn(receivePack); - factory = new BaseReceivePackFactory(handler, null) { + factory = new BaseReceivePackFactory(new GitChangesetConverterFactory(), handler, null) { @Override protected ReceivePack createBasicReceivePack(Object request, Repository repository) throws ServiceNotEnabledException, ServiceNotAuthorizedException { return wrappedReceivePackFactory.create(request, repository); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java index 01b0112b72..9ea196c951 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.client.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -32,6 +32,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import sonia.scm.repository.Changeset; import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.client.api.RepositoryClientException; //~--- JDK imports ------------------------------------------------------------ @@ -71,7 +72,8 @@ public class GitCommitCommand implements CommitCommand @Override public Changeset commit(CommitRequest request) throws IOException { - try (GitChangesetConverter converter = new GitChangesetConverter(git.getRepository())) + GitChangesetConverterFactory converterFactory = new GitChangesetConverterFactory(); + try (GitChangesetConverter converter = converterFactory.create(git.getRepository())) { RevCommit commit = git.commit() .setAuthor(request.getAuthor().getName(), request.getAuthor().getMail()) diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java index 01477e6bd9..36d0002de2 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java @@ -21,7 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- @@ -40,6 +40,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.rules.TemporaryFolder; import sonia.scm.repository.Changeset; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.Repository; import sonia.scm.user.User; @@ -110,23 +111,7 @@ public class AbstractRemoteCommandTestBase { // store reference to handle weak references - proto = new ScmTransportProtocol(new Provider() - { - - @Override - public HookEventFacade get() - { - return null; - } - }, new Provider() - { - - @Override - public GitRepositoryHandler get() - { - return null; - } - }); + proto = new ScmTransportProtocol(GitChangesetConverterFactory::new, () -> null, () -> null); Transport.register(proto); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java index cd2d537038..7e48c10b8c 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java @@ -21,12 +21,13 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ - + package sonia.scm.repository.spi; import org.eclipse.jgit.transport.ScmTransportProtocol; import org.eclipse.jgit.transport.Transport; import org.junit.rules.ExternalResource; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.RepositoryManager; @@ -47,7 +48,7 @@ public class BindTransportProtocolRule extends ExternalResource { RepositoryManager repositoryManager = mock(RepositoryManager.class); HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory); GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); - scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); + scmTransportProtocol = new ScmTransportProtocol(of(new GitChangesetConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler)); Transport.register(scmTransportProtocol); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java index 0ffa917a14..edf2500e1e 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java @@ -32,6 +32,7 @@ import org.junit.Ignore; import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; @@ -173,14 +174,11 @@ public class GitIncomingCommandTest assertEquals(0, cpr.getTotal()); } - /** - * Method description - * - * - * @return - */ - private GitIncomingCommand createCommand() - { - return new GitIncomingCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()))); + private GitIncomingCommand createCommand() { + return new GitIncomingCommand( + new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), + handler, + new GitChangesetConverterFactory() + ); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java index 6fcb9303cb..a6c08120e7 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java @@ -27,6 +27,7 @@ package sonia.scm.repository.spi; import org.junit.Test; import sonia.scm.NotFoundException; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverterFactory; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -108,8 +109,7 @@ public class GitLogCommandAncestorTest extends AbstractGitCommandTestBase createCommand().getChangesets(request); } - private GitLogCommand createCommand() - { - return new GitLogCommand(createContext()); + private GitLogCommand createCommand() { + return new GitLogCommand(createContext(), new GitChangesetConverterFactory()); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index 2a8c40db58..df3fd2a702 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -31,6 +31,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryConfig; import sonia.scm.repository.Modifications; import sonia.scm.repository.Person; @@ -293,8 +294,7 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase return new File(repositoryDirectory, "HEAD"); } - private GitLogCommand createCommand() - { - return new GitLogCommand(createContext()); + private GitLogCommand createCommand() { + return new GitLogCommand(createContext(), new GitChangesetConverterFactory()); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java index 5c7a491c72..890704841a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java @@ -31,6 +31,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; @@ -151,6 +152,10 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase */ private GitOutgoingCommand createCommand() { - return new GitOutgoingCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()))); + return new GitOutgoingCommand( + new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), + handler, + new GitChangesetConverterFactory() + ); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java index 9a32b5d01c..c8762bde01 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java @@ -34,6 +34,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; +import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.RepositoryManager; @@ -65,7 +66,7 @@ public class SimpleGitWorkingCopyFactoryTest extends AbstractGitCommandTestBase HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); HookEventFacade hookEventFacade = new HookEventFacade(of(mock(RepositoryManager.class)), hookContextFactory); GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); - proto = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler)); + proto = new ScmTransportProtocol(of(new GitChangesetConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler)); Transport.register(proto); workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); } From 64da67634846542eac217c045766c0c2e02ec2c4 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Wed, 22 Jul 2020 08:22:04 +0200 Subject: [PATCH 05/46] revisit gpg api and use it with from git plugin --- .../java/sonia/scm/repository/Changeset.java | 5 +- .../java/sonia/scm/repository/Signature.java | 5 +- .../security/{GPGPublicKey.java => GPG.java} | 37 ++- ...PublicKeyResolver.java => PrivateKey.java} | 25 +- .../java/sonia/scm/security/PublicKey.java | 69 ++++ .../scm/repository/GitChangesetConverter.java | 191 ++++------- .../GitChangesetConverterFactory.java | 14 +- .../git/BaseReceivePackFactoryTest.java | 4 +- .../repository/GitChangesetConverterTest.java | 303 ++++++++++++++++++ .../sonia/scm/repository/GitTestHelper.java | 65 ++++ .../client/spi/GitCommitCommand.java | 3 +- .../spi/AbstractRemoteCommandTestBase.java | 3 +- .../spi/BindTransportProtocolRule.java | 5 +- .../spi/GitIncomingCommandTest.java | 3 +- .../spi/GitLogCommandAncestorTest.java | 3 +- .../scm/repository/spi/GitLogCommandTest.java | 3 +- .../spi/GitOutgoingCommandTest.java | 3 +- .../spi/SimpleGitWorkingCopyFactoryTest.java | 3 +- 18 files changed, 587 insertions(+), 157 deletions(-) rename scm-core/src/main/java/sonia/scm/security/{GPGPublicKey.java => GPG.java} (58%) rename scm-core/src/main/java/sonia/scm/security/{GPGPublicKeyResolver.java => PrivateKey.java} (72%) create mode 100644 scm-core/src/main/java/sonia/scm/security/PublicKey.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitChangesetConverterTest.java create mode 100644 scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitTestHelper.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index 5edd01531e..4fb6aa4d6b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -86,7 +86,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { */ private Collection contributors; - private List signatures; + private List signatures = new ArrayList<>(); public Changeset() {} @@ -376,9 +376,6 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { * @since 2.3.0 */ public void addSignature(Signature signature) { - if (signatures == null) { - signatures = new ArrayList<>(); - } signatures.add(signature); } } diff --git a/scm-core/src/main/java/sonia/scm/repository/Signature.java b/scm-core/src/main/java/sonia/scm/repository/Signature.java index 4cada0e629..3e67613447 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Signature.java +++ b/scm-core/src/main/java/sonia/scm/repository/Signature.java @@ -26,6 +26,7 @@ package sonia.scm.repository; import lombok.Value; +import java.io.Serializable; import java.util.Optional; /** @@ -33,7 +34,9 @@ import java.util.Optional; * @since 2.3.0 */ @Value -public class Signature { +public class Signature implements Serializable { + + private static final long serialVersionUID = 1L; private final String key; private final String type; diff --git a/scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java b/scm-core/src/main/java/sonia/scm/security/GPG.java similarity index 58% rename from scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java rename to scm-core/src/main/java/sonia/scm/security/GPG.java index 731b958c09..c2cd6b8177 100644 --- a/scm-core/src/main/java/sonia/scm/security/GPGPublicKey.java +++ b/scm-core/src/main/java/sonia/scm/security/GPG.java @@ -24,17 +24,40 @@ package sonia.scm.security; -import lombok.Value; +import java.util.Optional; /** - * Public gpg key. + * Allows signing and verification using gpg. + * * @since 2.3.0 */ -@Value -public class GPGPublicKey { +public interface GPG { - private final String id; - private final String owner; - private final String raw; + /** + * Returns the id of the key from the given signature. + * @param signature signature + * @return public key id + */ + String findPublicKeyId(byte[] signature); + /** + * Returns the public key with the given id or an empty optional. + * @param id id of public + * @return public key or empty optional + */ + Optional findPublicKey(String id); + + /** + * Returns all public keys assigned to the given username + * @param username username of the public key owner + * @return collection of public keys + */ + Iterable findPublicKeysByUsername(String username); + + /** + * Returns the default private key of the currently authenticated user. + * + * @return default private key + */ + PrivateKey getPrivateKey(); } diff --git a/scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java b/scm-core/src/main/java/sonia/scm/security/PrivateKey.java similarity index 72% rename from scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java rename to scm-core/src/main/java/sonia/scm/security/PrivateKey.java index 56158ec36a..dc62a4ca74 100644 --- a/scm-core/src/main/java/sonia/scm/security/GPGPublicKeyResolver.java +++ b/scm-core/src/main/java/sonia/scm/security/PrivateKey.java @@ -24,25 +24,28 @@ package sonia.scm.security; -import java.util.List; -import java.util.Optional; +import java.io.ByteArrayInputStream; +import java.io.InputStream; /** - * Resolver for public gpg keys. + * Can be used to create signatures of data. * @since 2.3.0 */ -public interface GPGPublicKeyResolver { +public interface PrivateKey { /** - * Resolves the public key by its id. - * @param keyId id of the key - * @return public gpg key or empty optional. + * Creates a signature for the given data. + * @param stream data stream to sign + * @return signature */ - Optional byId(String keyId); + byte[] sign(InputStream stream); /** - * Resolves all public gpg keys for the given user. - * @return list of public gpg keys + * Creates a signature for the given data. + * @param data data to sign + * @return signature */ - List byUser(String username); + default byte[] sign(byte[] data) { + return sign(new ByteArrayInputStream(data)); + } } diff --git a/scm-core/src/main/java/sonia/scm/security/PublicKey.java b/scm-core/src/main/java/sonia/scm/security/PublicKey.java new file mode 100644 index 0000000000..30e3fe1072 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/PublicKey.java @@ -0,0 +1,69 @@ +/* + * 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.security; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Optional; + +/** + * The public key can be used to verify signatures. + * + * @since 2.3.0 + */ +public interface PublicKey { + + /** + * Returns id of the public key. + * + * @return id of key + */ + String getId(); + + /** + * Returns the username of the owner or an empty optional. + * + * @return owner or empty optional + */ + Optional getOwner(); + + /** + * Verifies that the signature is valid for the given data. + * @param stream stream of data to verify + * @param signature signature + * @return {@code true} if the signature is valid for the given data + */ + boolean verify(InputStream stream, byte[] signature); + + /** + * Verifies that the signature is valid for the given data. + * @param data data to verify + * @param signature signature + * @return {@code true} if the signature is valid for the given data + */ + default boolean verify(byte[] data, byte[] signature) { + return verify(new ByteArrayInputStream(data), signature); + } +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java index 3b8e6dadad..779fab0dbf 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverter.java @@ -33,16 +33,20 @@ import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.TreeWalk; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.eclipse.jgit.util.RawParseUtils; +import sonia.scm.security.GPG; +import sonia.scm.security.PublicKey; import sonia.scm.util.Util; +import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Optional; //~--- JDK imports ------------------------------------------------------------ @@ -50,121 +54,35 @@ import java.util.List; * * @author Sebastian Sdorra */ -public class GitChangesetConverter implements Closeable -{ +public class GitChangesetConverter implements Closeable { - /** - * the logger for GitChangesetConverter - */ - private static final Logger logger = - LoggerFactory.getLogger(GitChangesetConverter.class); - - //~--- constructors --------------------------------------------------------- - - /** - * Constructs ... - * - * - * @param repository - */ - GitChangesetConverter(org.eclipse.jgit.lib.Repository repository) - { - this(repository, null); - } - - /** - * Constructs ... - * - * - * @param repository - * @param revWalk - */ - GitChangesetConverter(org.eclipse.jgit.lib.Repository repository, - RevWalk revWalk) - { - this.repository = repository; - - if (revWalk != null) - { - this.revWalk = revWalk; - - } - else - { - this.revWalk = new RevWalk(repository); - } + private final GPG gpg; + private final Multimap tags; + private final TreeWalk treeWalk; + GitChangesetConverter(GPG gpg, org.eclipse.jgit.lib.Repository repository, RevWalk revWalk) { + this.gpg = gpg; this.tags = GitUtil.createTagMap(repository, revWalk); - treeWalk = new TreeWalk(repository); + this.treeWalk = new TreeWalk(repository); } - //~--- methods -------------------------------------------------------------- - - /** - * Method description - * - */ - @Override - public void close() - { - GitUtil.release(treeWalk); - } - - /** - * Method description - * - * - * @param commit - * - * @return - * - * @throws IOException - */ - public Changeset createChangeset(RevCommit commit) - { + public Changeset createChangeset(RevCommit commit) { return createChangeset(commit, Collections.emptyList()); } - /** - * Method description - * - * - * @param commit - * @param branch - * - * @return - * - * @throws IOException - */ - public Changeset createChangeset(RevCommit commit, String branch) - { + public Changeset createChangeset(RevCommit commit, String branch) { return createChangeset(commit, Lists.newArrayList(branch)); } - /** - * Method description - * - * - * - * @param commit - * @param branches - * - * @return - * - * @throws IOException - */ - public Changeset createChangeset(RevCommit commit, List branches) - { + public Changeset createChangeset(RevCommit commit, List branches) { String id = commit.getId().name(); List parentList = null; RevCommit[] parents = commit.getParents(); - if (Util.isNotEmpty(parents)) - { - parentList = new ArrayList(); + if (Util.isNotEmpty(parents)) { + parentList = new ArrayList<>(); - for (RevCommit parent : parents) - { + for (RevCommit parent : parents) { parentList.add(parent.getId().name()); } } @@ -175,8 +93,7 @@ public class GitChangesetConverter implements Closeable Person author = createPersonFor(authorIndent); String message = commit.getFullMessage(); - if (message != null) - { + if (message != null) { message = message.trim(); } @@ -185,41 +102,73 @@ public class GitChangesetConverter implements Closeable changeset.addContributor(new Contributor("Committed-by", createPersonFor(committerIdent))); } - if (parentList != null) - { + if (parentList != null) { changeset.setParents(parentList); } Collection tagCollection = tags.get(commit.getId()); - if (Util.isNotEmpty(tagCollection)) - { - + if (Util.isNotEmpty(tagCollection)) { // create a copy of the tag collection to reduce memory on caching changeset.getTags().addAll(Lists.newArrayList(tagCollection)); } changeset.setBranches(branches); + Signature signature = createSignature(commit); + if (signature != null) { + changeset.addSignature(signature); + } + return changeset; } + private static final byte[] GPG_HEADER = {'g', 'p', 'g', 's', 'i', 'g'}; + + private Signature createSignature(RevCommit commit) { + byte[] raw = commit.getRawBuffer(); + + int start = RawParseUtils.headerStart(GPG_HEADER, raw, 0); + if (start < 0) { + return null; + } + + int end = RawParseUtils.headerEnd(raw, start); + byte[] signature = Arrays.copyOfRange(raw, start, end); + + String publicKeyId = gpg.findPublicKeyId(signature); + + Optional publicKeyById = gpg.findPublicKey(publicKeyId); + if (!publicKeyById.isPresent()) { + // key not found + return new Signature(publicKeyId, "gpg", false, null); + } + + PublicKey publicKey = publicKeyById.get(); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + try { + byte[] headerPrefix = Arrays.copyOfRange(raw, 0, start - GPG_HEADER.length - 1); + baos.write(headerPrefix); + + byte[] headerSuffix = Arrays.copyOfRange(raw, end + 1, raw.length); + baos.write(headerSuffix); + } catch (IOException ex) { + // this will never happen, because we are writing into memory + throw new IllegalStateException("failed to write into memory", ex); + } + + boolean verified = publicKey.verify(baos.toByteArray(), signature); + return new Signature(publicKeyId, "gpg", verified, publicKey.getOwner().orElse(null)); + } + public Person createPersonFor(PersonIdent personIndent) { return new Person(personIndent.getName(), personIndent.getEmailAddress()); } + @Override + public void close() { + GitUtil.release(treeWalk); + } - //~--- fields --------------------------------------------------------------- - - /** Field description */ - private org.eclipse.jgit.lib.Repository repository; - - /** Field description */ - private RevWalk revWalk; - - /** Field description */ - private Multimap tags; - - /** Field description */ - private TreeWalk treeWalk; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java index c5eb6e312a..4f4389fa2e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitChangesetConverterFactory.java @@ -26,15 +26,25 @@ package sonia.scm.repository; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevWalk; +import sonia.scm.security.GPG; + +import javax.inject.Inject; public class GitChangesetConverterFactory { + private final GPG gpg; + + @Inject + public GitChangesetConverterFactory(GPG gpg) { + this.gpg = gpg; + } + public GitChangesetConverter create(Repository repository) { - return new GitChangesetConverter(repository); + return new GitChangesetConverter(gpg, repository, new RevWalk(repository)); } public GitChangesetConverter create(Repository repository, RevWalk revWalk) { - return new GitChangesetConverter(repository, revWalk); + return new GitChangesetConverter(gpg, repository, revWalk); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java index d6de6a0780..cc178c2ef8 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/protocolcommand/git/BaseReceivePackFactoryTest.java @@ -38,9 +38,9 @@ import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; -import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitConfig; import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.GitTestHelper; import sonia.scm.web.CollectingPackParserListener; import sonia.scm.web.GitReceiveHook; @@ -83,7 +83,7 @@ public class BaseReceivePackFactoryTest { ReceivePack receivePack = new ReceivePack(repository); when(wrappedReceivePackFactory.create(request, repository)).thenReturn(receivePack); - factory = new BaseReceivePackFactory(new GitChangesetConverterFactory(), handler, null) { + factory = new BaseReceivePackFactory(GitTestHelper.createConverterFactory(), handler, null) { @Override protected ReceivePack createBasicReceivePack(Object request, Repository repository) throws ServiceNotEnabledException, ServiceNotAuthorizedException { return wrappedReceivePackFactory.create(request, repository); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitChangesetConverterTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitChangesetConverterTest.java new file mode 100644 index 0000000000..3a2a624fb5 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitChangesetConverterTest.java @@ -0,0 +1,303 @@ +/* + * 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; + +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.HashAlgorithmTags; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.lib.CommitBuilder; +import org.eclipse.jgit.lib.GpgSignature; +import org.eclipse.jgit.lib.GpgSigner; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.io.TempDir; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.security.GPG; +import sonia.scm.security.PublicKey; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.util.Date; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class GitChangesetConverterTest { + + private static Git git; + + @BeforeAll + static void setUpRepository(@TempDir Path repositoryPath) throws GitAPIException { + // we use the same repository for all tests to speed things up + git = Git.init().setDirectory(repositoryPath.toFile()).call(); + } + + @AfterAll + static void closeRepository() { + git.close(); + } + + @Test + void shouldConvertChangeset() throws GitAPIException, IOException { + long now = System.currentTimeMillis() - 1000L; + Changeset changeset = commit( + "Tricia McMillan", "trillian@hitchhiker.com", "Added awesome markdown file" + ); + assertThat(changeset.getId()).isNotEmpty(); + assertThat(changeset.getDate()).isGreaterThanOrEqualTo(now); + assertThat(changeset.getDescription()).isEqualTo("Added awesome markdown file"); + + Person author = changeset.getAuthor(); + assertThat(author.getName()).isEqualTo("Tricia McMillan"); + assertThat(author.getMail()).isEqualTo("trillian@hitchhiker.com"); + } + + private Changeset commit(String name, String mail, String message) throws GitAPIException, IOException { + addRandomFileToRepository(); + + RevCommit commit = git.commit() + .setAuthor(name, mail) + .setMessage(message) + .call(); + + GitChangesetConverterFactory converterFactory = GitTestHelper.createConverterFactory(); + return converterFactory.create(git.getRepository()).createChangeset(commit); + } + + private void addRandomFileToRepository() throws IOException, GitAPIException { + File directory = git.getRepository().getWorkTree(); + String name = UUID.randomUUID().toString(); + File file = new File(directory, name + ".md"); + Files.write(file.toPath(), ("# Greetings\n\nFrom " + name).getBytes(StandardCharsets.UTF_8)); + git.add().addFilepattern(name + ".md").call(); + } + + @Nested + class SignatureTests { + + @Mock + private GPG gpg; + @Mock + private PublicKey publicKey; + + private PGPKeyPair keyPair; + private GpgSigner defaultSigner; + + @BeforeEach + void setUpTestingSignerAndCaptureDefault() throws Exception { + defaultSigner = GpgSigner.getDefault(); + // we use the same keypair for all tests to speed things up a little bit + if (keyPair == null) { + keyPair = createKeyPair(); + GpgSigner.setDefault(new TestingGpgSigner(keyPair)); + } + } + + @AfterEach + void restoreDefaultSigner() { + GpgSigner.setDefault(defaultSigner); + } + + @Test + void shouldReturnUnknownSignature() throws Exception { + String identity = "0xAWESOMExBOB"; + when(gpg.findPublicKeyId(any())).thenReturn(identity); + + Signature signature = addSignedCommitAndReturnSignature(identity); + assertThat(signature).isEqualTo(new Signature(identity, "gpg", false, null)); + } + + @Test + void shouldReturnKnownButInvalidSignature() throws Exception { + String identity = "0xAWESOMExBOB"; + String owner = "BobTheSigner"; + setPublicKey(identity, owner, false); + + Signature signature = addSignedCommitAndReturnSignature(identity); + assertThat(signature).isEqualTo(new Signature(identity, "gpg", false, owner)); + } + + @Test + void shouldReturnValidSignature() throws Exception { + String identity = "0xAWESOMExBOB"; + String owner = "BobTheSigner"; + setPublicKey(identity, owner, true); + + Signature signature = addSignedCommitAndReturnSignature(identity); + assertThat(signature).isEqualTo(new Signature(identity, "gpg", true, owner)); + } + + @Test + void shouldPassDataAndSignatureForVerification() throws Exception { + setPublicKey("0x42", "Me", true); + addSignedCommitAndReturnSignature("0x42"); + + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(byte[].class); + ArgumentCaptor signatureCaptor = ArgumentCaptor.forClass(byte[].class); + verify(publicKey).verify(dataCaptor.capture(), signatureCaptor.capture()); + + String data = new String(dataCaptor.getValue()); + assertThat(data).contains("author Bob The Signer "); + + String signature = new String(signatureCaptor.getValue()); + assertThat(signature).contains("BEGIN PGP SIGNATURE", "END PGP SIGNATURE"); + } + + @Test + void shouldNotReturnSignatureForNonSignedCommit() throws GitAPIException, IOException { + Changeset changeset = commit("Bob", "unsigned@bob.de", "not signed"); + assertThat(changeset.getSignatures()).isEmpty(); + } + + private void setPublicKey(String id, String owner, boolean valid) { + when(gpg.findPublicKeyId(any())).thenReturn(id); + when(gpg.findPublicKey(id)).thenReturn(Optional.of(publicKey)); + + when(publicKey.getOwner()).thenReturn(Optional.of(owner)); + when(publicKey.verify(any(byte[].class), any(byte[].class))).thenReturn(valid); + } + + private Signature addSignedCommitAndReturnSignature(String keyIdentity) throws IOException, GitAPIException { + RevCommit commit = addSignedCommit(keyIdentity); + GitChangesetConverterFactory factory = new GitChangesetConverterFactory(gpg); + GitChangesetConverter converter = factory.create(git.getRepository()); + + List signatures = converter.createChangeset(commit).getSignatures(); + assertThat(signatures).isNotEmpty().hasSize(1); + + return signatures.get(0); + } + + private RevCommit addSignedCommit(String keyIdentity) throws IOException, GitAPIException { + addRandomFileToRepository(); + return git.commit() + .setAuthor("Bob The Signer", "sign@bob.de") + .setMessage("Signed from Bob") + .setSign(true) + .setSigningKey(keyIdentity) + .call(); + } + + + } + private PGPKeyPair createKeyPair() throws PGPException, NoSuchProviderException, NoSuchAlgorithmException { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); + // we use a small key size to speedup test, a much larger size should be used for production + keyPairGenerator.initialize(512); + KeyPair pair = keyPairGenerator.generateKeyPair(); + return new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, pair, new Date()); + } + + private static class TestingGpgSigner extends GpgSigner { + + private final PGPKeyPair keyPair; + + TestingGpgSigner(PGPKeyPair keyPair) { + this.keyPair = keyPair; + } + + @Override + public boolean canLocateSigningKey(String gpgSigningKey, PersonIdent committer, CredentialsProvider credentialsProvider) { + return true; + } + + @Override + public void sign(CommitBuilder commit, String gpgSigningKey, + PersonIdent committer, CredentialsProvider credentialsProvider) { + try { + if (keyPair == null) { + throw new JGitInternalException(JGitText.get().unableToSignCommitNoSecretKey); + } + + PGPPrivateKey privateKey = keyPair.getPrivateKey(); + + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( + new JcaPGPContentSignerBuilder( + keyPair.getPublicKey().getAlgorithm(), + HashAlgorithmTags.SHA256).setProvider(BouncyCastleProvider.PROVIDER_NAME) + ); + signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + try (BCPGOutputStream out = new BCPGOutputStream(new ArmoredOutputStream(buffer))) { + signatureGenerator.update(commit.build()); + signatureGenerator.generate().encode(out); + } + commit.setGpgSignature(new GpgSignature(buffer.toByteArray())); + } catch (PGPException | IOException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + + } + + // register bouncy castle provider on load + static { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + } +} + + diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitTestHelper.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitTestHelper.java new file mode 100644 index 0000000000..cdf70db55d --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/GitTestHelper.java @@ -0,0 +1,65 @@ +/* + * 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; + +import sonia.scm.security.GPG; +import sonia.scm.security.PrivateKey; +import sonia.scm.security.PublicKey; + +import java.util.Collections; +import java.util.Optional; + +public final class GitTestHelper { + + private GitTestHelper() { + } + + public static GitChangesetConverterFactory createConverterFactory() { + return new GitChangesetConverterFactory(new NoopGPG()); + } + + private static class NoopGPG implements GPG { + + @Override + public String findPublicKeyId(byte[] signature) { + return "secret-key"; + } + + @Override + public Optional findPublicKey(String id) { + return Optional.empty(); + } + + @Override + public Iterable findPublicKeysByUsername(String username) { + return Collections.emptySet(); + } + + @Override + public PrivateKey getPrivateKey() { + return null; + } + } +} diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java index 9ea196c951..d17267b056 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/client/spi/GitCommitCommand.java @@ -33,6 +33,7 @@ import org.eclipse.jgit.revwalk.RevCommit; import sonia.scm.repository.Changeset; import sonia.scm.repository.GitChangesetConverter; import sonia.scm.repository.GitChangesetConverterFactory; +import sonia.scm.repository.GitTestHelper; import sonia.scm.repository.client.api.RepositoryClientException; //~--- JDK imports ------------------------------------------------------------ @@ -72,7 +73,7 @@ public class GitCommitCommand implements CommitCommand @Override public Changeset commit(CommitRequest request) throws IOException { - GitChangesetConverterFactory converterFactory = new GitChangesetConverterFactory(); + GitChangesetConverterFactory converterFactory = GitTestHelper.createConverterFactory(); try (GitChangesetConverter converter = converterFactory.create(git.getRepository())) { RevCommit commit = git.commit() diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java index 36d0002de2..208d9a16bf 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/AbstractRemoteCommandTestBase.java @@ -42,6 +42,7 @@ import org.junit.rules.TemporaryFolder; import sonia.scm.repository.Changeset; import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.GitTestHelper; import sonia.scm.repository.Repository; import sonia.scm.user.User; import sonia.scm.user.UserTestData; @@ -111,7 +112,7 @@ public class AbstractRemoteCommandTestBase { // store reference to handle weak references - proto = new ScmTransportProtocol(GitChangesetConverterFactory::new, () -> null, () -> null); + proto = new ScmTransportProtocol(GitTestHelper::createConverterFactory, () -> null, () -> null); Transport.register(proto); } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java index 7e48c10b8c..f859efecae 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/BindTransportProtocolRule.java @@ -29,6 +29,7 @@ import org.eclipse.jgit.transport.Transport; import org.junit.rules.ExternalResource; import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.GitTestHelper; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.api.HookContextFactory; @@ -43,12 +44,12 @@ public class BindTransportProtocolRule extends ExternalResource { private ScmTransportProtocol scmTransportProtocol; @Override - protected void before() throws Throwable { + protected void before() { HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); RepositoryManager repositoryManager = mock(RepositoryManager.class); HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory); GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); - scmTransportProtocol = new ScmTransportProtocol(of(new GitChangesetConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler)); + scmTransportProtocol = new ScmTransportProtocol(of(GitTestHelper.createConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler)); Transport.register(scmTransportProtocol); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java index edf2500e1e..5cfab8de24 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitIncomingCommandTest.java @@ -33,6 +33,7 @@ import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverterFactory; +import sonia.scm.repository.GitTestHelper; import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; @@ -178,7 +179,7 @@ public class GitIncomingCommandTest return new GitIncomingCommand( new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), handler, - new GitChangesetConverterFactory() + GitTestHelper.createConverterFactory() ); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java index a6c08120e7..38c1516187 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandAncestorTest.java @@ -28,6 +28,7 @@ import org.junit.Test; import sonia.scm.NotFoundException; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverterFactory; +import sonia.scm.repository.GitTestHelper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -110,6 +111,6 @@ public class GitLogCommandAncestorTest extends AbstractGitCommandTestBase } private GitLogCommand createCommand() { - return new GitLogCommand(createContext(), new GitChangesetConverterFactory()); + return new GitLogCommand(createContext(), GitTestHelper.createConverterFactory()); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java index df3fd2a702..b15c6ecebd 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitLogCommandTest.java @@ -33,6 +33,7 @@ import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryConfig; +import sonia.scm.repository.GitTestHelper; import sonia.scm.repository.Modifications; import sonia.scm.repository.Person; @@ -295,6 +296,6 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase } private GitLogCommand createCommand() { - return new GitLogCommand(createContext(), new GitChangesetConverterFactory()); + return new GitLogCommand(createContext(), GitTestHelper.createConverterFactory()); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java index 890704841a..22ccb94e1c 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitOutgoingCommandTest.java @@ -32,6 +32,7 @@ import org.junit.Test; import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider; import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.GitChangesetConverterFactory; +import sonia.scm.repository.GitTestHelper; import sonia.scm.store.InMemoryConfigurationStoreFactory; import java.io.IOException; @@ -155,7 +156,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase return new GitOutgoingCommand( new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())), handler, - new GitChangesetConverterFactory() + GitTestHelper.createConverterFactory() ); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java index c8762bde01..2d814bba2a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/SimpleGitWorkingCopyFactoryTest.java @@ -36,6 +36,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import sonia.scm.repository.GitChangesetConverterFactory; import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.GitTestHelper; import sonia.scm.repository.PreProcessorUtil; import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.api.HookContextFactory; @@ -66,7 +67,7 @@ public class SimpleGitWorkingCopyFactoryTest extends AbstractGitCommandTestBase HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class)); HookEventFacade hookEventFacade = new HookEventFacade(of(mock(RepositoryManager.class)), hookContextFactory); GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class); - proto = new ScmTransportProtocol(of(new GitChangesetConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler)); + proto = new ScmTransportProtocol(of(GitTestHelper.createConverterFactory()), of(hookEventFacade), of(gitRepositoryHandler)); Transport.register(proto); workdirProvider = new WorkdirProvider(temporaryFolder.newFolder()); } From cf1ef3b570c0d1a01e33ffdc9ba26106605819dc Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Thu, 23 Jul 2020 13:34:37 +0200 Subject: [PATCH 06/46] start implementation of store api --- pom.xml | 21 +++ scm-webapp/pom.xml | 17 +++ .../sonia/scm/security/gpg/GPGException.java | 35 +++++ .../java/sonia/scm/security/gpg/Keys.java | 86 ++++++++++++ .../scm/security/gpg/PublicKeyStore.java | 86 ++++++++++++ .../sonia/scm/security/gpg/RawGpgKey.java | 74 +++++++++++ .../sonia/scm/security/gpg/GPGTestHelper.java | 44 +++++++ .../java/sonia/scm/security/gpg/KeysTest.java | 49 +++++++ .../scm/security/gpg/PublicKeyStoreTest.java | 67 ++++++++++ .../sonia/scm/security/gpg/single.asc | 30 +++++ .../sonia/scm/security/gpg/subkeys.asc | 122 ++++++++++++++++++ 11 files changed, 631 insertions(+) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/GPGException.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/gpg/GPGTestHelper.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/gpg/KeysTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java create mode 100644 scm-webapp/src/test/resources/sonia/scm/security/gpg/single.asc create mode 100644 scm-webapp/src/test/resources/sonia/scm/security/gpg/subkeys.asc diff --git a/pom.xml b/pom.xml index 0335226c4a..11b51fb99c 100644 --- a/pom.xml +++ b/pom.xml @@ -525,6 +525,26 @@ 1.14 + + + + org.bouncycastle + bcpg-jdk15on + ${bouncycastle.version} + + + + org.bouncycastle + bcprov-jdk15on + ${bouncycastle.version} + + + + org.bouncycastle + bcpkix-jdk15on + ${bouncycastle.version} + + @@ -899,6 +919,7 @@ 4.2.3 2.3.3 6.1.5.Final + 1.65 1.6.2 diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index 5daec406b4..eb83eaeb11 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -114,6 +114,23 @@ ${jjwt.version} + + + + org.bouncycastle + bcpg-jdk15on + + + + org.bouncycastle + bcprov-jdk15on + + + + org.bouncycastle + bcpkix-jdk15on + + diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/GPGException.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/GPGException.java new file mode 100644 index 0000000000..da278f03da --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/GPGException.java @@ -0,0 +1,35 @@ +/* + * 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.security.gpg; + +public class GPGException extends RuntimeException { + GPGException(String message) { + super(message); + } + + GPGException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java new file mode 100644 index 0000000000..7ecbd1d49d --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java @@ -0,0 +1,86 @@ +/* + * 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.security.gpg; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.gpg.keybox.PublicKeyRingBlob; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPObjectFactory; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRing; +import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; +import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; +import sonia.scm.security.PublicKey; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; + +final class Keys { + + private static final KeyFingerPrintCalculator calculator = new JcaKeyFingerprintCalculator(); + + private Keys() {} + + static String resolveIdFromKey(String rawKey) throws IOException, PGPException { + List keys = collectKeys(rawKey); + if (keys.size() > 1) { + keys = keys.stream().filter(PGPPublicKey::isMasterKey).collect(Collectors.toList()); + } + if (keys.isEmpty()) { + throw new IllegalArgumentException("found multiple keys, but no master keys"); + } + if (keys.size() > 1) { + throw new IllegalArgumentException("found multiple master keys"); + } + + PGPPublicKey pgpPublicKey = keys.get(0); + return createId(pgpPublicKey); + } + + private static String createId(PGPPublicKey pgpPublicKey) { + return "0x" + Long.toHexString(pgpPublicKey.getKeyID()).toUpperCase(Locale.ENGLISH); + } + + private static List collectKeys(String rawKey) throws IOException, PGPException { + List publicKeys = new ArrayList<>(); + InputStream decoderStream = PGPUtil.getDecoderStream(new ByteArrayInputStream(rawKey.getBytes(StandardCharsets.UTF_8))); + PGPPublicKeyRingCollection collection = new PGPPublicKeyRingCollection(decoderStream, calculator); + for (PGPPublicKeyRing pgpPublicKeys : collection) { + for (PGPPublicKey pgpPublicKey : pgpPublicKeys) { + publicKeys.add(pgpPublicKey); + } + } + return publicKeys; + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java new file mode 100644 index 0000000000..394e657e59 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java @@ -0,0 +1,86 @@ +/* + * 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.security.gpg; + +import com.google.common.annotations.VisibleForTesting; +import org.apache.shiro.SecurityUtils; +import org.bouncycastle.openpgp.PGPException; +import sonia.scm.security.PublicKey; +import sonia.scm.store.DataStore; +import sonia.scm.store.DataStoreFactory; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.io.IOException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +@Singleton +public class PublicKeyStore { + + private static final String STORE_NAME = "gpg_public_keys"; + + private final DataStore store; + private final Supplier currentUserSupplier; + + @Inject + public PublicKeyStore(DataStoreFactory dataStoreFactory) { + this( + dataStoreFactory.withType(RawGpgKey.class).withName(STORE_NAME).build(), + () -> SecurityUtils.getSubject().getPrincipal().toString() + ); + } + + @VisibleForTesting + PublicKeyStore(DataStore store, Supplier currentUserSupplier) { + this.store = store; + this.currentUserSupplier = currentUserSupplier; + } + + public RawGpgKey add(String displayName, String rawKey) { + try { + String id = Keys.resolveIdFromKey(rawKey); + RawGpgKey key = new RawGpgKey(id, displayName, currentUserSupplier.get(), rawKey, Instant.now()); + + store.put(id, key); + + return key; + } catch (IOException | PGPException e) { + throw new GPGException("failed to resolve id from gpg key"); + } + } + + public Optional findById(String id) { + return store.getOptional(id); + } + +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java new file mode 100644 index 0000000000..5b098c5af3 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKey.java @@ -0,0 +1,74 @@ +/* + * 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.security.gpg; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import sonia.scm.xml.XmlInstantAdapter; + +import javax.xml.bind.annotation.XmlAccessType; +import javax.xml.bind.annotation.XmlAccessorType; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.Instant; +import java.util.Objects; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@XmlAccessorType(XmlAccessType.FIELD) +public class RawGpgKey { + + private String id; + private String displayName; + private String owner; + private String raw; + + @XmlJavaTypeAdapter(XmlInstantAdapter.class) + private Instant created; + + RawGpgKey(String id) { + this.id = id; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + RawGpgKey that = (RawGpgKey) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return Objects.hash(id); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/GPGTestHelper.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/GPGTestHelper.java new file mode 100644 index 0000000000..4608aace5c --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/GPGTestHelper.java @@ -0,0 +1,44 @@ +/* + * 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.security.gpg; + +import com.google.common.io.Resources; + +import java.io.IOException; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +final class GPGTestHelper { + + private GPGTestHelper() { + } + + @SuppressWarnings("UnstableApiUsage") + static String readKey(String key) throws IOException { + URL resource = Resources.getResource("sonia/scm/security/gpg/" + key); + return Resources.toString(resource, StandardCharsets.UTF_8); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/KeysTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/KeysTest.java new file mode 100644 index 0000000000..765674d0f2 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/KeysTest.java @@ -0,0 +1,49 @@ +/* + * 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.security.gpg; + +import org.bouncycastle.openpgp.PGPException; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static sonia.scm.security.gpg.GPGTestHelper.readKey; + +class KeysTest { + + @Test + void shouldResolveId() throws IOException, PGPException { + String rawPublicKey = readKey("single.asc"); + assertThat(Keys.resolveIdFromKey(rawPublicKey)).isEqualTo("0x975922F193B07D6E"); + } + + @Test + void shouldResolveIdFromMasterKey() throws IOException, PGPException { + String rawPublicKey = readKey("subkeys.asc"); + assertThat(Keys.resolveIdFromKey(rawPublicKey)).isEqualTo("0x13B13D4C8A9350A1"); + } + +} diff --git a/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java new file mode 100644 index 0000000000..3c26256247 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyStoreTest.java @@ -0,0 +1,67 @@ +/* + * 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.security.gpg; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import sonia.scm.store.InMemoryDataStore; + +import java.io.IOException; +import java.time.Instant; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; + +class PublicKeyStoreTest { + + private PublicKeyStore keyStore; + + @BeforeEach + void setUpKeyStore() { + keyStore = new PublicKeyStore(new InMemoryDataStore<>(), () -> "trillian"); + } + + @Test + void shouldReturnStoredKey() throws IOException { + String rawKey = GPGTestHelper.readKey("single.asc"); + Instant now = Instant.now(); + + RawGpgKey key = keyStore.add("SCM Package Key", rawKey); + assertThat(key.getId()).isEqualTo("0x975922F193B07D6E"); + assertThat(key.getDisplayName()).isEqualTo("SCM Package Key"); + assertThat(key.getOwner()).isEqualTo("trillian"); + assertThat(key.getCreated()).isAfterOrEqualTo(now); + assertThat(key.getRaw()).isEqualTo(rawKey); + } + + @Test + void shouldFindStoredKeyById() throws IOException { + String rawKey = GPGTestHelper.readKey("single.asc"); + keyStore.add("SCM Package Key", rawKey); + Optional key = keyStore.findById("0x975922F193B07D6E"); + assertThat(key).isPresent(); + } + +} diff --git a/scm-webapp/src/test/resources/sonia/scm/security/gpg/single.asc b/scm-webapp/src/test/resources/sonia/scm/security/gpg/single.asc new file mode 100644 index 0000000000..8c06de15d9 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/security/gpg/single.asc @@ -0,0 +1,30 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBF69VQEBEADcnNUucubBBn0nEubrNV3SWh1CbiUyhLU6TEKSUbyB2gDcOrMR +4wAeqK0ar4cloIpS5YEtjT/qVpERzabbJe0NSfCwYblpdfoA0idcvi7Ssnczfr/i +1cF0gqmAjoDqAaSk7xHa/mxiEwAUAXGDWu4pCksT8gHDDx/7lIkHvPZs+VGyMM/O +NVc3QET0JMuKhBYJafSJkdUl3qjX9N6ykETQIbxSv0YLjZuAzQJggLoiMWZtDkrU +KuXh/bJja2xnzj9XtMmpkrIRAX1VYLlI7sJ17b1Tv/yIvaZ1akccRSFtx/kqcM86 +LSO77E82EfLhuVeVCvzxzgfrYHVVX6oFhAEzKUI0TRT453yGZuC5j91HXP3VuXjH +opAODeQLbDfcWPH8joegDZBuK1dQRvVcyh5CMSAWHw9vgXKergrMHVB4EVIqMMir +Q29KfbSuhA5D+xLxPjphbDsMIcLMy0ADd7N0ydmpt2x7ES3Sx3iTssibBYQB731Z +DQCgxy8mdL4MSCNUkDziGyqK+cI9/jRehiOimFsnQIDqQ1hOQBw7M21lvGlbn1IA +iKosi9rb+tUtnNT2d3byjvjZzMj1xoJeOs9i+xEDu1224xEEfJIixfSLLW2T8Qdb +6/a5XGENQB9ZfcW0CrK+V+bHLKXkY2MG9mAL3KEgmDDqydQTwOGNFzJHpwARAQAB +tE9TQ00gUGFja2FnZXMgKHNpZ25pbmcga2V5IGZvciBwYWNrYWdlcy5zY20tbWFu +YWdlci5vcmcpIDxzY20tdGVhbUBjbG91ZG9ndS5jb20+iQJOBBMBCgA4FiEEI9Ji +WyNeJaRxmHWil1ki8ZOwfW4FAl69VQECGy8FCwkIBwMFFQoJCAsFFgIDAQACHgEC +F4AACgkQl1ki8ZOwfW49vg/9EYZSEejdfuzLWcC1M8C9lyausvB+SAI7fEcnD4do +w6WEdnPTus5aAnr1qOncH3aJpjwqfIpuCMdS94i9jgLJTLaQ8S2WegLFVhDQvC7v +Q6ZieOUAYVWJx2Klq2OT1MVJPEzskV3QtFBTaHmuseJrGvH0Waw26MGw8MiAPyES +oZGdcULZBwpr8nazqcFXFuDxMFr1Y28sEzW/ntfScLnIVIVXAWaAXq/4dtB1cIIc +KKsszkM018HdEPSf99ry/nqiwGkOBqMUiEM9+VIMuJRs+BSvT9ETM0yx21fYV2Jj +YG20ahsd1tRFwYLLyzwukT9KUBydZ2RZP+L0gkC+WrfMxvreQxP7d8PH8aF2Ii1e +SpLs91h97tXq+ucp6YyGTEsVnajQeGSA0mX/AhOe3swBNZ08vuhSWkKjKnOXqR4h +IyJaJGAuo6vd+GzdAu/9MxWZQZTWERauofxLTzESwJl7WfTgEFvF+7hNCkQmUA7r +oGc8ahEvuGCZG2MtfBPSPL51FifDlO+G0rifWqHuocZWdBX6fcmt+SYb8SHaG0cu +JP35uWVGuva+Bw73+S21xU3yIjt8bTkZpIuHO9xhivXIOVR3jVhB1V6KrI+jlZl/ +DmQc8MI3+Ez6Hh3kVQjokL1W/u/gg8XumCmc+4hq2QIOSF/ODUMg0b1nne4msLc/ +H1Q= +=5/Nh +-----END PGP PUBLIC KEY BLOCK----- diff --git a/scm-webapp/src/test/resources/sonia/scm/security/gpg/subkeys.asc b/scm-webapp/src/test/resources/sonia/scm/security/gpg/subkeys.asc new file mode 100644 index 0000000000..eaacc24062 --- /dev/null +++ b/scm-webapp/src/test/resources/sonia/scm/security/gpg/subkeys.asc @@ -0,0 +1,122 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=dKpI +-----END PGP PUBLIC KEY BLOCK----- From 37f3c15b84a7f45b1e4fa4c0b39d99f071066ca3 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 24 Jul 2020 11:57:06 +0200 Subject: [PATCH 07/46] fixed npe on build --- pom.xml | 4 ++-- scm-webapp/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 11b51fb99c..f7eff5138d 100644 --- a/pom.xml +++ b/pom.xml @@ -615,7 +615,7 @@ org.apache.maven.plugins maven-enforcer-plugin - 3.0.0-M1 + 3.0.0-M3 enforce-java @@ -659,7 +659,7 @@ org.codehaus.mojo extra-enforcer-rules - 1.0-beta-7 + 1.3 diff --git a/scm-webapp/pom.xml b/scm-webapp/pom.xml index eb83eaeb11..28bf55743f 100644 --- a/scm-webapp/pom.xml +++ b/scm-webapp/pom.xml @@ -661,7 +661,7 @@ org.basepom.maven duplicate-finder-maven-plugin - 1.3.0 + 1.4.0 default From 13326d62535f7e776449d0fbe8237bbfd96dfa1f Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 24 Jul 2020 11:58:28 +0200 Subject: [PATCH 08/46] set since tag for gpg stuff to 2.4.0, rename Signature.key to Signature.keyId and added DummyGPG for testing --- .../java/sonia/scm/repository/Changeset.java | 6 +- .../java/sonia/scm/repository/Signature.java | 4 +- .../src/main/java/sonia/scm/security/GPG.java | 2 +- .../java/sonia/scm/security/PrivateKey.java | 2 +- .../java/sonia/scm/security/PublicKey.java | 2 +- .../java/sonia/scm/security/gpg/DummyGPG.java | 58 +++++++++++++++++++ .../sonia/scm/security/gpg/GPGModule.java | 37 ++++++++++++ .../java/sonia/scm/security/gpg/Keys.java | 5 -- .../scm/security/gpg/PublicKeyStore.java | 7 --- 9 files changed, 103 insertions(+), 20 deletions(-) create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/DummyGPG.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/GPGModule.java diff --git a/scm-core/src/main/java/sonia/scm/repository/Changeset.java b/scm-core/src/main/java/sonia/scm/repository/Changeset.java index 4fb6aa4d6b..914fd8f90a 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Changeset.java +++ b/scm-core/src/main/java/sonia/scm/repository/Changeset.java @@ -355,7 +355,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { /** * Sets a collection of signatures which belong to this changeset. * @param signatures collection of signatures - * @since 2.3.0 + * @since 2.4.0 */ public void setSignatures(Collection signatures) { this.signatures = new ArrayList<>(signatures); @@ -364,7 +364,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { /** * Returns a immutable list of signatures. * @return signatures - * @since 2.3.0 + * @since 2.4.0 */ public List getSignatures() { return Collections.unmodifiableList(signatures); @@ -373,7 +373,7 @@ public class Changeset extends BasicPropertiesAware implements ModelObject { /** * Adds a signature to the list of signatures. * @param signature - * @since 2.3.0 + * @since 2.4.0 */ public void addSignature(Signature signature) { signatures.add(signature); diff --git a/scm-core/src/main/java/sonia/scm/repository/Signature.java b/scm-core/src/main/java/sonia/scm/repository/Signature.java index 3e67613447..b077793f7b 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Signature.java +++ b/scm-core/src/main/java/sonia/scm/repository/Signature.java @@ -31,14 +31,14 @@ import java.util.Optional; /** * Signature is the output of a signature verification. - * @since 2.3.0 + * @since 2.4.0 */ @Value public class Signature implements Serializable { private static final long serialVersionUID = 1L; - private final String key; + private final String keyId; private final String type; private final boolean verified; private final String owner; diff --git a/scm-core/src/main/java/sonia/scm/security/GPG.java b/scm-core/src/main/java/sonia/scm/security/GPG.java index c2cd6b8177..2fa773c906 100644 --- a/scm-core/src/main/java/sonia/scm/security/GPG.java +++ b/scm-core/src/main/java/sonia/scm/security/GPG.java @@ -29,7 +29,7 @@ import java.util.Optional; /** * Allows signing and verification using gpg. * - * @since 2.3.0 + * @since 2.4.0 */ public interface GPG { diff --git a/scm-core/src/main/java/sonia/scm/security/PrivateKey.java b/scm-core/src/main/java/sonia/scm/security/PrivateKey.java index dc62a4ca74..b1d12582a7 100644 --- a/scm-core/src/main/java/sonia/scm/security/PrivateKey.java +++ b/scm-core/src/main/java/sonia/scm/security/PrivateKey.java @@ -29,7 +29,7 @@ import java.io.InputStream; /** * Can be used to create signatures of data. - * @since 2.3.0 + * @since 2.4.0 */ public interface PrivateKey { diff --git a/scm-core/src/main/java/sonia/scm/security/PublicKey.java b/scm-core/src/main/java/sonia/scm/security/PublicKey.java index 30e3fe1072..734b19afc1 100644 --- a/scm-core/src/main/java/sonia/scm/security/PublicKey.java +++ b/scm-core/src/main/java/sonia/scm/security/PublicKey.java @@ -31,7 +31,7 @@ import java.util.Optional; /** * The public key can be used to verify signatures. * - * @since 2.3.0 + * @since 2.4.0 */ public interface PublicKey { diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/DummyGPG.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/DummyGPG.java new file mode 100644 index 0000000000..79c6e516bb --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/DummyGPG.java @@ -0,0 +1,58 @@ +/* + * 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.security.gpg; + +import sonia.scm.security.GPG; +import sonia.scm.security.PrivateKey; +import sonia.scm.security.PublicKey; + +import java.util.Collections; +import java.util.Optional; + +/** + * Dummy implementation of {@link GPG} should be replaced soon. + */ +public class DummyGPG implements GPG { + + @Override + public String findPublicKeyId(byte[] signature) { + return "unknown"; + } + + @Override + public Optional findPublicKey(String id) { + return Optional.empty(); + } + + @Override + public Iterable findPublicKeysByUsername(String username) { + return Collections.emptySet(); + } + + @Override + public PrivateKey getPrivateKey() { + throw new UnsupportedOperationException("getPrivateKey is not yet implemented"); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/GPGModule.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/GPGModule.java new file mode 100644 index 0000000000..c064e54ba7 --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/GPGModule.java @@ -0,0 +1,37 @@ +/* + * 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.security.gpg; + +import com.google.inject.AbstractModule; +import sonia.scm.plugin.Extension; +import sonia.scm.security.GPG; + +@Extension +public class GPGModule extends AbstractModule { + @Override + protected void configure() { + bind(GPG.class).to(DummyGPG.class); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java index 7ecbd1d49d..26f2d94403 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/Keys.java @@ -24,18 +24,13 @@ package sonia.scm.security.gpg; -import org.bouncycastle.bcpg.ArmoredInputStream; -import org.bouncycastle.gpg.keybox.PublicKeyRingBlob; import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; import org.bouncycastle.openpgp.PGPUtil; -import org.bouncycastle.openpgp.jcajce.JcaPGPPublicKeyRing; import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator; import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator; -import sonia.scm.security.PublicKey; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java index 394e657e59..ffb54b355a 100644 --- a/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java +++ b/scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyStore.java @@ -27,20 +27,13 @@ package sonia.scm.security.gpg; import com.google.common.annotations.VisibleForTesting; import org.apache.shiro.SecurityUtils; import org.bouncycastle.openpgp.PGPException; -import sonia.scm.security.PublicKey; import sonia.scm.store.DataStore; import sonia.scm.store.DataStoreFactory; import javax.inject.Inject; import javax.inject.Singleton; -import javax.xml.bind.annotation.XmlAccessType; -import javax.xml.bind.annotation.XmlAccessorType; -import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlRootElement; import java.io.IOException; import java.time.Instant; -import java.util.ArrayList; -import java.util.List; import java.util.Optional; import java.util.function.Supplier; From 4290ca4077839da29995979dcedac18a4a879e71 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Fri, 24 Jul 2020 14:59:28 +0200 Subject: [PATCH 09/46] add config form for public keys --- .../scm/security/NotPublicKeyException.java | 45 +++++ .../src/main/java/sonia/scm/user/User.java | 2 +- scm-ui/ui-webapp/public/locales/de/users.json | 11 +- scm-ui/ui-webapp/public/locales/en/users.json | 11 +- scm-ui/ui-webapp/src/containers/Profile.tsx | 4 + .../navLinks/SetPublicKeysNavLink.tsx | 43 ++++ .../src/users/components/navLinks/index.ts | 1 + .../components/publicKeys/AddPublicKey.tsx | 89 ++++++++ .../components/publicKeys/PublicKeyEntry.tsx | 61 ++++++ .../components/publicKeys/PublicKeyTable.tsx | 62 ++++++ .../components/publicKeys/SetPublicKeys.tsx | 94 +++++++++ .../publicKeys/formatPublicKey.test.ts | 51 +++++ .../components/publicKeys/formatPublicKey.ts | 43 ++++ .../src/users/containers/SingleUser.tsx | 9 +- .../scm/api/v2/resources/MapperModule.java | 2 + .../scm/api/v2/resources/MeDtoFactory.java | 3 + .../scm/api/v2/resources/ResourceLinks.java | 7 + .../api/v2/resources/UserToUserDtoMapper.java | 1 + .../gpg/PublicKeyCollectionMapper.java | 86 ++++++++ .../scm/security/gpg/PublicKeyMapper.java | 79 ++++++++ .../scm/security/gpg/PublicKeyResource.java | 191 ++++++++++++++++++ .../scm/security/gpg/PublicKeyStore.java | 43 ++-- .../sonia/scm/security/gpg/RawGpgKey.java | 2 + .../sonia/scm/security/gpg/RawGpgKeyDto.java | 48 +++++ .../api/v2/resources/MeDtoFactoryTest.java | 22 ++ .../gpg/PublicKeyCollectionMapperTest.java | 110 ++++++++++ .../scm/security/gpg/PublicKeyMapperTest.java | 92 +++++++++ .../security/gpg/PublicKeyResourceTest.java | 142 +++++++++++++ .../scm/security/gpg/PublicKeyStoreTest.java | 85 +++++++- 29 files changed, 1416 insertions(+), 23 deletions(-) create mode 100644 scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java create mode 100644 scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx create mode 100644 scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx create mode 100644 scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyEntry.tsx create mode 100644 scm-ui/ui-webapp/src/users/components/publicKeys/PublicKeyTable.tsx create mode 100644 scm-ui/ui-webapp/src/users/components/publicKeys/SetPublicKeys.tsx create mode 100644 scm-ui/ui-webapp/src/users/components/publicKeys/formatPublicKey.test.ts create mode 100644 scm-ui/ui-webapp/src/users/components/publicKeys/formatPublicKey.ts create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyCollectionMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyMapper.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/PublicKeyResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/security/gpg/RawGpgKeyDto.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyCollectionMapperTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyMapperTest.java create mode 100644 scm-webapp/src/test/java/sonia/scm/security/gpg/PublicKeyResourceTest.java diff --git a/scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java b/scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java new file mode 100644 index 0000000000..2433b31429 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/security/NotPublicKeyException.java @@ -0,0 +1,45 @@ +/* + * 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.security; + +import sonia.scm.BadRequestException; +import sonia.scm.ContextEntry; + +import java.util.List; + +public class NotPublicKeyException extends BadRequestException { + public NotPublicKeyException(List context, String message) { + super(context, message); + } + + public NotPublicKeyException(List context, String message, Exception cause) { + super(context, message, cause); + } + + @Override + public String getCode() { + return "BxS5wX2v71"; + } +} diff --git a/scm-core/src/main/java/sonia/scm/user/User.java b/scm-core/src/main/java/sonia/scm/user/User.java index cd2afb282d..1b0a3b2388 100644 --- a/scm-core/src/main/java/sonia/scm/user/User.java +++ b/scm-core/src/main/java/sonia/scm/user/User.java @@ -50,7 +50,7 @@ import java.security.Principal; @StaticPermissions( value = "user", globalPermissions = {"create", "list", "autocomplete"}, - permissions = {"read", "modify", "delete", "changePassword"}, + permissions = {"read", "modify", "delete", "changePassword", "changePublicKeys"}, custom = true, customGlobal = true ) @XmlRootElement(name = "users") diff --git a/scm-ui/ui-webapp/public/locales/de/users.json b/scm-ui/ui-webapp/public/locales/de/users.json index fd744e768d..8cb79d6d6f 100644 --- a/scm-ui/ui-webapp/public/locales/de/users.json +++ b/scm-ui/ui-webapp/public/locales/de/users.json @@ -37,7 +37,8 @@ "settingsNavLink": "Einstellungen", "generalNavLink": "Generell", "setPasswordNavLink": "Passwort", - "setPermissionsNavLink": "Berechtigungen" + "setPermissionsNavLink": "Berechtigungen", + "setPublicKeyNavLink": "Öffentliche Schlüssel" } }, "createUser": { @@ -60,5 +61,13 @@ "userForm": { "subtitle": "Benutzer bearbeiten", "button": "Speichern" + }, + "publicKey": { + "noStoredKeys": "Es wurden keine Schlüssel gefunden.", + "displayName": "Anzeigename", + "raw": "Schlüssel", + "created": "Eingetragen an", + "addKey": "Schlüssel hinzufügen", + "delete": "Löschen" } } diff --git a/scm-ui/ui-webapp/public/locales/en/users.json b/scm-ui/ui-webapp/public/locales/en/users.json index 3353977a18..0db2b9175f 100644 --- a/scm-ui/ui-webapp/public/locales/en/users.json +++ b/scm-ui/ui-webapp/public/locales/en/users.json @@ -37,7 +37,8 @@ "settingsNavLink": "Settings", "generalNavLink": "General", "setPasswordNavLink": "Password", - "setPermissionsNavLink": "Permissions" + "setPermissionsNavLink": "Permissions", + "setPublicKeyNavLink": "Public Keys" } }, "createUser": { @@ -60,5 +61,13 @@ "userForm": { "subtitle": "Edit User", "button": "Submit" + }, + "publicKey": { + "noStoredKeys": "No keys found.", + "displayName": "Display Name", + "raw": "Key", + "created": "Created on", + "addKey": "Add key", + "delete": "Delete" } } diff --git a/scm-ui/ui-webapp/src/containers/Profile.tsx b/scm-ui/ui-webapp/src/containers/Profile.tsx index 8f14bed1e7..e0700306f8 100644 --- a/scm-ui/ui-webapp/src/containers/Profile.tsx +++ b/scm-ui/ui-webapp/src/containers/Profile.tsx @@ -42,6 +42,8 @@ import { import ChangeUserPassword from "./ChangeUserPassword"; import ProfileInfo from "./ProfileInfo"; import { ExtensionPoint } from "@scm-manager/ui-extensions"; +import SetPublicKeys from "../users/components/publicKeys/SetPublicKeys"; +import SetPublicKeyNavLink from "../users/components/navLinks/SetPublicKeysNavLink"; type Props = RouteComponentProps & WithTranslation & { @@ -93,6 +95,7 @@ class Profile extends React.Component { } /> } /> + } /> @@ -109,6 +112,7 @@ class Profile extends React.Component { title={t("profile.settingsNavLink")} > + diff --git a/scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx b/scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx new file mode 100644 index 0000000000..f3144442f5 --- /dev/null +++ b/scm-ui/ui-webapp/src/users/components/navLinks/SetPublicKeysNavLink.tsx @@ -0,0 +1,43 @@ +/* + * 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 { Link, User, Me } from "@scm-manager/ui-types"; +import { NavLink } from "@scm-manager/ui-components"; +import { useTranslation } from "react-i18next"; + +type Props = { + user: User | Me; + publicKeyUrl: string; +}; + +const SetPublicKeyNavLink: FC = ({ user, publicKeyUrl }) => { + const [t] = useTranslation("users"); + + if ((user?._links?.publicKeys as Link)?.href) { + return ; + } + return null; +}; + +export default SetPublicKeyNavLink; diff --git a/scm-ui/ui-webapp/src/users/components/navLinks/index.ts b/scm-ui/ui-webapp/src/users/components/navLinks/index.ts index 0ccd16b42a..f732ea83ee 100644 --- a/scm-ui/ui-webapp/src/users/components/navLinks/index.ts +++ b/scm-ui/ui-webapp/src/users/components/navLinks/index.ts @@ -25,3 +25,4 @@ export { default as EditUserNavLink } from "./EditUserNavLink"; export { default as SetPasswordNavLink } from "./SetPasswordNavLink"; export { default as SetPermissionsNavLink } from "./SetPermissionsNavLink"; +export { default as SetPublicKeysNavLink } from "./SetPublicKeysNavLink"; diff --git a/scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx b/scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx new file mode 100644 index 0000000000..e0129e8245 --- /dev/null +++ b/scm-ui/ui-webapp/src/users/components/publicKeys/AddPublicKey.tsx @@ -0,0 +1,89 @@ +/* + * 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, useState } from "react"; +import { User, Link, Links, Collection } from "@scm-manager/ui-types/src"; +import { + ErrorNotification, + InputField, + Level, + Textarea, + SubmitButton, + apiClient, + Loading +} from "@scm-manager/ui-components"; +import { useTranslation } from "react-i18next"; +import { CONTENT_TYPE_PUBLIC_KEY } from "./SetPublicKeys"; + +type Props = { + createLink: string; + refresh: () => void; +}; + +const AddPublicKey: FC = ({ createLink, refresh }) => { + const [t] = useTranslation("users"); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(); + const [displayName, setDisplayName] = useState(""); + const [raw, setRaw] = useState(""); + + const isValid = () => { + return !!displayName && !!raw; + }; + + const resetForm = () => { + setDisplayName(""); + setRaw(""); + }; + + const addKey = () => { + setLoading(true); + apiClient + .post(createLink, { displayName: displayName, raw: raw }, CONTENT_TYPE_PUBLIC_KEY) + .then(resetForm) + .then(refresh) + .then(() => setLoading(false)) + .catch(setError); + }; + + if (error) { + return ; + } + + if (loading) { + return ; + } + + return ( + <> + +