2025-09-25 17:16:17 +02:00
|
|
|
From d6ebbc2783508164f749ce4c47e74c7bb45f5b7e Mon Sep 17 00:00:00 2001
|
2025-09-22 17:56:49 +02:00
|
|
|
From: =?UTF-8?q?Fridrich=20=C5=A0trba?= <fridrich.strba@bluewin.ch>
|
|
|
|
|
Date: Mon, 22 Sep 2025 17:41:31 +0200
|
2025-09-25 17:16:17 +02:00
|
|
|
Subject: [PATCH] Don't depend on apache-sshd test-jars
|
2025-09-22 17:56:49 +02:00
|
|
|
|
|
|
|
|
---
|
|
|
|
|
.../maven-scm-provider-gittest/pom.xml | 12 -
|
|
|
|
|
.../git/BogusPasswordAuthenticator.java | 47 ++
|
|
|
|
|
.../provider/git/CommandExecutionHelper.java | 91 ++++
|
|
|
|
|
.../provider/git/CommonTestSupportUtils.java | 504 ++++++++++++++++++
|
2025-09-25 17:16:17 +02:00
|
|
|
.../provider/git/CoreTestSupportUtils.java | 91 ++++
|
2025-09-22 17:56:49 +02:00
|
|
|
.../maven/scm/provider/git/EchoShell.java | 40 ++
|
|
|
|
|
.../scm/provider/git/EchoShellFactory.java | 41 ++
|
|
|
|
|
.../maven/scm/provider/git/GitSshServer.java | 2 -
|
2025-09-25 17:16:17 +02:00
|
|
|
8 files changed, 814 insertions(+), 14 deletions(-)
|
2025-09-22 17:56:49 +02:00
|
|
|
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java
|
|
|
|
|
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java
|
|
|
|
|
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java
|
|
|
|
|
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java
|
|
|
|
|
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java
|
|
|
|
|
create mode 100644 maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java
|
|
|
|
|
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
|
|
|
|
|
index 97a4a84bc..bd894bedb 100644
|
|
|
|
|
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/pom.xml
|
|
|
|
|
@@ -50,18 +50,6 @@
|
|
|
|
|
<artifactId>sshd-git</artifactId>
|
|
|
|
|
<version>${minaSshdVersion}</version>
|
|
|
|
|
</dependency>
|
|
|
|
|
- <dependency>
|
|
|
|
|
- <groupId>org.apache.sshd</groupId>
|
|
|
|
|
- <artifactId>sshd-common</artifactId>
|
|
|
|
|
- <version>${minaSshdVersion}</version>
|
|
|
|
|
- <type>test-jar</type>
|
|
|
|
|
- </dependency>
|
|
|
|
|
- <dependency>
|
|
|
|
|
- <groupId>org.apache.sshd</groupId>
|
|
|
|
|
- <artifactId>sshd-core</artifactId>
|
|
|
|
|
- <version>${minaSshdVersion}</version>
|
|
|
|
|
- <type>test-jar</type>
|
|
|
|
|
- </dependency>
|
|
|
|
|
<!-- for creating SSH keypairs dynamically -->
|
|
|
|
|
<dependency>
|
|
|
|
|
<groupId>org.bouncycastle</groupId>
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 000000000..1bcfa2e1d
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/BogusPasswordAuthenticator.java
|
|
|
|
|
@@ -0,0 +1,47 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
|
|
+ * distributed with this work for additional information
|
|
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+ *
|
|
|
|
|
+ * Unless required by applicable law or agreed to in writing,
|
|
|
|
|
+ * software distributed under the License is distributed on an
|
|
|
|
|
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
+ * KIND, either express or implied. See the License for the
|
|
|
|
|
+ * specific language governing permissions and limitations
|
|
|
|
|
+ * under the License.
|
|
|
|
|
+ */
|
|
|
|
|
+package org.apache.maven.scm.provider.git;
|
|
|
|
|
+
|
|
|
|
|
+import org.apache.sshd.common.util.logging.AbstractLoggingBean;
|
|
|
|
|
+import org.apache.sshd.server.auth.password.PasswordAuthenticator;
|
|
|
|
|
+import org.apache.sshd.server.session.ServerSession;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * A test {@link PasswordAuthenticator} that accepts an authentication attempt if the username is not {@code null} and
|
|
|
|
|
+ * same as password
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
|
|
|
+ */
|
|
|
|
|
+public class BogusPasswordAuthenticator extends AbstractLoggingBean implements PasswordAuthenticator {
|
|
|
|
|
+ public static final BogusPasswordAuthenticator INSTANCE = new BogusPasswordAuthenticator();
|
|
|
|
|
+
|
|
|
|
|
+ public BogusPasswordAuthenticator() {
|
|
|
|
|
+ super();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public boolean authenticate(String username, String password, ServerSession session) {
|
|
|
|
|
+ boolean result = (username != null) && username.equals(password);
|
|
|
|
|
+ if (log.isDebugEnabled()) {
|
|
|
|
|
+ log.debug("authenticate({}) {} / {} - success={}", session, username, password, result);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return result;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 000000000..75011879a
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommandExecutionHelper.java
|
|
|
|
|
@@ -0,0 +1,91 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
|
|
+ * distributed with this work for additional information
|
|
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+ *
|
|
|
|
|
+ * Unless required by applicable law or agreed to in writing,
|
|
|
|
|
+ * software distributed under the License is distributed on an
|
|
|
|
|
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
+ * KIND, either express or implied. See the License for the
|
|
|
|
|
+ * specific language governing permissions and limitations
|
|
|
|
|
+ * under the License.
|
|
|
|
|
+ */
|
|
|
|
|
+package org.apache.maven.scm.provider.git;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.BufferedReader;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.InputStreamReader;
|
|
|
|
|
+import java.io.InterruptedIOException;
|
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+
|
|
|
|
|
+import org.apache.sshd.server.command.AbstractCommandSupport;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
|
|
|
+ */
|
|
|
|
|
+public abstract class CommandExecutionHelper extends AbstractCommandSupport {
|
|
|
|
|
+ protected CommandExecutionHelper() {
|
|
|
|
|
+ this(null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ protected CommandExecutionHelper(String command) {
|
|
|
|
|
+ super(command, null);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public void run() {
|
|
|
|
|
+ String command = getCommand();
|
|
|
|
|
+ try {
|
|
|
|
|
+ if (command == null) {
|
|
|
|
|
+ try (BufferedReader r =
|
|
|
|
|
+ new BufferedReader(new InputStreamReader(getInputStream(), StandardCharsets.UTF_8))) {
|
|
|
|
|
+ for (; ; ) {
|
|
|
|
|
+ command = r.readLine();
|
|
|
|
|
+ if (command == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!handleCommandLine(command)) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ handleCommandLine(command);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (InterruptedIOException e) {
|
|
|
|
|
+ // Ignore - signaled end
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ String message =
|
|
|
|
|
+ "Failed (" + e.getClass().getSimpleName() + ") to handle '" + command + "': " + e.getMessage();
|
|
|
|
|
+ try {
|
|
|
|
|
+ OutputStream stderr = getErrorStream();
|
|
|
|
|
+ stderr.write(message.getBytes(StandardCharsets.US_ASCII));
|
|
|
|
|
+ } catch (IOException ioe) {
|
|
|
|
|
+ log.warn(
|
|
|
|
|
+ "Failed ({}) to write error message={}: {}",
|
|
|
|
|
+ e.getClass().getSimpleName(),
|
|
|
|
|
+ message,
|
|
|
|
|
+ ioe.getMessage());
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ onExit(-1, message);
|
|
|
|
|
+ }
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ onExit(0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param command The command line
|
|
|
|
|
+ * @return {@code true} if continue accepting command
|
|
|
|
|
+ * @throws Exception If failed to handle the command line
|
|
|
|
|
+ */
|
|
|
|
|
+ protected abstract boolean handleCommandLine(String command) throws Exception;
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 000000000..be4fdda4f
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CommonTestSupportUtils.java
|
|
|
|
|
@@ -0,0 +1,504 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
|
|
+ * distributed with this work for additional information
|
|
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+ *
|
|
|
|
|
+ * Unless required by applicable law or agreed to in writing,
|
|
|
|
|
+ * software distributed under the License is distributed on an
|
|
|
|
|
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
+ * KIND, either express or implied. See the License for the
|
|
|
|
|
+ * specific language governing permissions and limitations
|
|
|
|
|
+ * under the License.
|
|
|
|
|
+ */
|
|
|
|
|
+package org.apache.maven.scm.provider.git;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.net.MalformedURLException;
|
|
|
|
|
+import java.net.URI;
|
|
|
|
|
+import java.net.URISyntaxException;
|
|
|
|
|
+import java.net.URL;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.nio.file.Paths;
|
|
|
|
|
+import java.security.CodeSource;
|
|
|
|
|
+import java.security.GeneralSecurityException;
|
|
|
|
|
+import java.security.KeyPair;
|
|
|
|
|
+import java.security.KeyPairGenerator;
|
|
|
|
|
+import java.security.ProtectionDomain;
|
|
|
|
|
+import java.security.spec.InvalidKeySpecException;
|
|
|
|
|
+import java.util.Arrays;
|
|
|
|
|
+import java.util.Collection;
|
|
|
|
|
+import java.util.Collections;
|
|
|
|
|
+import java.util.Iterator;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Map;
|
|
|
|
|
+import java.util.Objects;
|
|
|
|
|
+import java.util.concurrent.ConcurrentHashMap;
|
|
|
|
|
+import java.util.concurrent.atomic.AtomicReference;
|
|
|
|
|
+
|
|
|
|
|
+import org.apache.sshd.common.cipher.ECCurves;
|
|
|
|
|
+import org.apache.sshd.common.config.keys.KeyUtils;
|
|
|
|
|
+import org.apache.sshd.common.keyprovider.FileKeyPairProvider;
|
|
|
|
|
+import org.apache.sshd.common.keyprovider.KeyIdentityProvider;
|
|
|
|
|
+import org.apache.sshd.common.keyprovider.KeyPairProvider;
|
|
|
|
|
+import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
|
|
|
|
|
+import org.apache.sshd.common.util.GenericUtils;
|
|
|
|
|
+import org.apache.sshd.common.util.ValidateUtils;
|
|
|
|
|
+import org.apache.sshd.common.util.security.SecurityUtils;
|
|
|
|
|
+import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * TODO Add javadoc
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
|
|
|
+ */
|
|
|
|
|
+public final class CommonTestSupportUtils {
|
|
|
|
|
+ /**
|
|
|
|
|
+ * URL/URI scheme that refers to a file
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String FILE_URL_SCHEME = "file";
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Prefix used in URL(s) that reference a file resource
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String FILE_URL_PREFIX = FILE_URL_SCHEME + ":";
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Separator used in URL(s) that reference a resource inside a JAR to denote the sub-path inside the JAR
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final char RESOURCE_SUBPATH_SEPARATOR = '!';
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Suffix of JAR files
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String JAR_FILE_SUFFIX = ".jar";
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * URL/URI scheme that refers to a JAR
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String JAR_URL_SCHEME = "jar";
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Prefix used in URL(s) that reference a resource inside a JAR
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String JAR_URL_PREFIX = JAR_URL_SCHEME + ":";
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Suffix of compile Java class files
|
|
|
|
|
+ */
|
|
|
|
|
+ public static final String CLASS_FILE_SUFFIX = ".class";
|
|
|
|
|
+
|
|
|
|
|
+ public static final List<String> TARGET_FOLDER_NAMES = // NOTE: order is important
|
|
|
|
|
+ Collections.unmodifiableList(Arrays.asList("target" /* Maven */, "build" /* Gradle */));
|
|
|
|
|
+
|
|
|
|
|
+ public static final String DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM = KeyUtils.EC_ALGORITHM;
|
|
|
|
|
+ public static final int DEFAULT_TEST_HOST_KEY_SIZE = 256;
|
|
|
|
|
+ public static final String DEFAULT_TEST_HOST_KEY_TYPE =
|
|
|
|
|
+ ECCurves.fromCurveSize(DEFAULT_TEST_HOST_KEY_SIZE).getKeyType();
|
|
|
|
|
+
|
|
|
|
|
+ // uses a cached instance to avoid re-creating the keys as it is a time-consuming effort
|
|
|
|
|
+ private static final AtomicReference<KeyPairProvider> KEYPAIR_PROVIDER_HOLDER = new AtomicReference<>();
|
|
|
|
|
+ // uses a cached instance to avoid re-creating the keys as it is a time-consuming effort
|
|
|
|
|
+ private static final Map<String, FileKeyPairProvider> PROVIDERS_MAP = new ConcurrentHashMap<>();
|
|
|
|
|
+
|
|
|
|
|
+ private CommonTestSupportUtils() {
|
|
|
|
|
+ throw new UnsupportedOperationException("No instance allowed");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param clazz A {@link Class} object
|
|
|
|
|
+ * @return A {@link URI} to the location of the class bytes container - e.g., the root folder,
|
|
|
|
|
+ * the containing JAR, etc.. Returns {@code null} if location could not be resolved
|
|
|
|
|
+ * @throws URISyntaxException if location is not a valid URI
|
|
|
|
|
+ * @see #getClassContainerLocationURL(Class)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static URI getClassContainerLocationURI(Class<?> clazz) throws URISyntaxException {
|
|
|
|
|
+ URL url = getClassContainerLocationURL(clazz);
|
|
|
|
|
+ return (url == null) ? null : url.toURI();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param clazz A {@link Class} object
|
|
|
|
|
+ * @return A {@link URL} to the location of the class bytes container - e.g., the root folder, the containing
|
|
|
|
|
+ * JAR, etc.. Returns {@code null} if location could not be resolved
|
|
|
|
|
+ */
|
|
|
|
|
+ public static URL getClassContainerLocationURL(Class<?> clazz) {
|
|
|
|
|
+ ProtectionDomain pd = clazz.getProtectionDomain();
|
|
|
|
|
+ CodeSource cs = (pd == null) ? null : pd.getCodeSource();
|
|
|
|
|
+ URL url = (cs == null) ? null : cs.getLocation();
|
|
|
|
|
+ if (url == null) {
|
|
|
|
|
+ url = getClassBytesURL(clazz);
|
|
|
|
|
+ if (url == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String srcForm = getURLSource(url);
|
|
|
|
|
+ if (GenericUtils.isEmpty(srcForm)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ url = new URL(srcForm);
|
|
|
|
|
+ } catch (MalformedURLException e) {
|
|
|
|
|
+ throw new IllegalArgumentException("getClassContainerLocationURL(" + clazz.getName() + ")"
|
|
|
|
|
+ + " Failed to create URL=" + srcForm + " from " + url.toExternalForm()
|
|
|
|
|
+ + ": " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return url;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param uri The {@link URI} value - ignored if {@code null}
|
|
|
|
|
+ * @return The URI(s) source path where {@link #JAR_URL_PREFIX} and any sub-resource are stripped
|
|
|
|
|
+ * @see #getURLSource(String)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String getURLSource(URI uri) {
|
|
|
|
|
+ return getURLSource((uri == null) ? null : uri.toString());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param url The {@link URL} value - ignored if {@code null}
|
|
|
|
|
+ * @return The URL(s) source path where {@link #JAR_URL_PREFIX} and any sub-resource are stripped
|
|
|
|
|
+ * @see #getURLSource(String)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String getURLSource(URL url) {
|
|
|
|
|
+ return getURLSource((url == null) ? null : url.toExternalForm());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param externalForm The {@link URL#toExternalForm()} string - ignored if {@code null}/empty
|
|
|
|
|
+ * @return The URL(s) source path where {@link #JAR_URL_PREFIX} and any sub-resource are stripped
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String getURLSource(String externalForm) {
|
|
|
|
|
+ String url = externalForm;
|
|
|
|
|
+ if (GenericUtils.isEmpty(url)) {
|
|
|
|
|
+ return url;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ url = stripJarURLPrefix(externalForm);
|
|
|
|
|
+ if (GenericUtils.isEmpty(url)) {
|
|
|
|
|
+ return url;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int sepPos = url.indexOf(RESOURCE_SUBPATH_SEPARATOR);
|
|
|
|
|
+ if (sepPos < 0) {
|
|
|
|
|
+ return adjustURLPathValue(url);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return adjustURLPathValue(url.substring(0, sepPos));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param url A {@link URL} - ignored if {@code null}
|
|
|
|
|
+ * @return The path after stripping any trailing '/' provided the path is not '/' itself
|
|
|
|
|
+ * @see #adjustURLPathValue(String)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String adjustURLPathValue(URL url) {
|
|
|
|
|
+ return adjustURLPathValue((url == null) ? null : url.getPath());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param path A URL path value - ignored if {@code null}/empty
|
|
|
|
|
+ * @return The path after stripping any trailing '/' provided the path is not '/' itself
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String adjustURLPathValue(final String path) {
|
|
|
|
|
+ final int pathLen = (path == null) ? 0 : path.length();
|
|
|
|
|
+ if ((pathLen <= 1) || (path.charAt(pathLen - 1) != '/')) {
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return path.substring(0, pathLen - 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static String stripJarURLPrefix(String externalForm) {
|
|
|
|
|
+ String url = externalForm;
|
|
|
|
|
+ if (GenericUtils.isEmpty(url)) {
|
|
|
|
|
+ return url;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (url.startsWith(JAR_URL_PREFIX)) {
|
|
|
|
|
+ return url.substring(JAR_URL_PREFIX.length());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return url;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param clazz The request {@link Class}
|
|
|
|
|
+ * @return A {@link URL} to the location of the <code>.class</code> file - {@code null} if location could not
|
|
|
|
|
+ * be resolved
|
|
|
|
|
+ */
|
|
|
|
|
+ public static URL getClassBytesURL(Class<?> clazz) {
|
|
|
|
|
+ String className = clazz.getName();
|
|
|
|
|
+ int sepPos = className.indexOf('$');
|
|
|
|
|
+ // if this is an internal class, then need to use its parent as well
|
|
|
|
|
+ if (sepPos > 0) {
|
|
|
|
|
+ sepPos = className.lastIndexOf('.');
|
|
|
|
|
+ if (sepPos > 0) {
|
|
|
|
|
+ className = className.substring(sepPos + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ className = clazz.getSimpleName();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return clazz.getResource(className + CLASS_FILE_SUFFIX);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static String getClassBytesResourceName(Class<?> clazz) {
|
|
|
|
|
+ return getClassBytesResourceName((clazz == null) ? null : clazz.getName());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param name The fully qualified class name - ignored if {@code null}/empty
|
|
|
|
|
+ * @return The relative path of the class file byte-code resource
|
|
|
|
|
+ */
|
|
|
|
|
+ public static String getClassBytesResourceName(String name) {
|
|
|
|
|
+ if (GenericUtils.isEmpty(name)) {
|
|
|
|
|
+ return name;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return name.replace('.', '/') + CLASS_FILE_SUFFIX;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Path resolve(Path root, String... children) {
|
|
|
|
|
+ if (GenericUtils.isEmpty(children)) {
|
|
|
|
|
+ return root;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return resolve(root, Arrays.asList(children));
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Path resolve(Path root, Collection<String> children) {
|
|
|
|
|
+ Path path = root;
|
|
|
|
|
+ if (!GenericUtils.isEmpty(children)) {
|
|
|
|
|
+ for (String child : children) {
|
|
|
|
|
+ path = path.resolve(child);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param anchor An anchor {@link Class} whose container we want to use as the starting point for the
|
|
|
|
|
+ * "target" folder lookup up the hierarchy
|
|
|
|
|
+ * @return The "target" <U>folder</U> - {@code null} if not found
|
|
|
|
|
+ * @see #detectTargetFolder(Path)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Path detectTargetFolder(Class<?> anchor) {
|
|
|
|
|
+ Path path = detectTargetFolder(getClassContainerLocationPath(anchor));
|
|
|
|
|
+ if (path == null) {
|
|
|
|
|
+ String basedir = System.getProperty("basedir");
|
|
|
|
|
+ path = detectTargetFolder(Paths.get(basedir, "target"));
|
|
|
|
|
+ }
|
|
|
|
|
+ return path;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param clazz A {@link Class} object
|
|
|
|
|
+ * @return A {@link Path} of the location of the class bytes container - e.g., the root
|
|
|
|
|
+ * folder, the containing JAR, etc.. Returns {@code null} if location could not be
|
|
|
|
|
+ * resolved
|
|
|
|
|
+ * @throws IllegalArgumentException If location is not a valid {@link Path} location
|
|
|
|
|
+ * @see #getClassContainerLocationURI(Class)
|
|
|
|
|
+ * @see #toPathSource(URI)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Path getClassContainerLocationPath(Class<?> clazz) throws IllegalArgumentException {
|
|
|
|
|
+ try {
|
|
|
|
|
+ URI uri = getClassContainerLocationURI(clazz);
|
|
|
|
|
+ return toPathSource(uri);
|
|
|
|
|
+ } catch (URISyntaxException | MalformedURLException e) {
|
|
|
|
|
+ throw new IllegalArgumentException(e.getClass().getSimpleName() + ": " + e.getMessage(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Converts a {@link URL} that may refer to an internal resource to a {@link Path} representing is
|
|
|
|
|
+ * "source" container (e.g., if it is a resource in a JAR, then the result is the JAR's path)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param url The {@link URL} - ignored if {@code null}
|
|
|
|
|
+ * @return The matching {@link Path}
|
|
|
|
|
+ * @throws MalformedURLException If source URL does not refer to a file location
|
|
|
|
|
+ * @see #toPathSource(URI)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Path toPathSource(URL url) throws MalformedURLException {
|
|
|
|
|
+ if (url == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ return toPathSource(url.toURI());
|
|
|
|
|
+ } catch (URISyntaxException e) {
|
|
|
|
|
+ throw new MalformedURLException("toFileSource(" + url.toExternalForm() + ")"
|
|
|
|
|
+ + " cannot (" + e.getClass().getSimpleName() + ")"
|
|
|
|
|
+ + " convert to URI: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Converts a {@link URI} that may refer to an internal resource to a {@link Path} representing is
|
|
|
|
|
+ * "source" container (e.g., if it is a resource in a JAR, then the result is the JAR's path)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param uri The {@link URI} - ignored if {@code null}
|
|
|
|
|
+ * @return The matching {@link Path}
|
|
|
|
|
+ * @throws MalformedURLException If source URI does not refer to a file location
|
|
|
|
|
+ * @see #getURLSource(URI)
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Path toPathSource(URI uri) throws MalformedURLException {
|
|
|
|
|
+ String src = getURLSource(uri);
|
|
|
|
|
+ if (GenericUtils.isEmpty(src)) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!src.startsWith(FILE_URL_PREFIX)) {
|
|
|
|
|
+ throw new MalformedURLException("toFileSource(" + src + ") not a '" + FILE_URL_SCHEME + "' scheme");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Paths.get(new URI(src));
|
|
|
|
|
+ } catch (URISyntaxException e) {
|
|
|
|
|
+ throw new MalformedURLException("toFileSource(" + src + ")"
|
|
|
|
|
+ + " cannot (" + e.getClass().getSimpleName() + ")"
|
|
|
|
|
+ + " convert to URI: " + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * @param anchorFile An anchor {@link Path} we want to use as the starting point for the "target" or
|
|
|
|
|
+ * "build" folder lookup up the hierarchy
|
|
|
|
|
+ * @return The "target" <U>folder</U> - {@code null} if not found
|
|
|
|
|
+ */
|
|
|
|
|
+ public static Path detectTargetFolder(Path anchorFile) {
|
|
|
|
|
+ for (Path file = anchorFile; file != null; file = file.getParent()) {
|
|
|
|
|
+ if (!Files.isDirectory(file)) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ String name = Objects.toString(file.getFileName(), "");
|
|
|
|
|
+ if (TARGET_FOLDER_NAMES.contains(name)) {
|
|
|
|
|
+ return file;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static KeyPair generateKeyPair(String algorithm, int keySize) throws GeneralSecurityException {
|
|
|
|
|
+ KeyPairGenerator gen = SecurityUtils.getKeyPairGenerator(algorithm);
|
|
|
|
|
+ if (KeyUtils.EC_ALGORITHM.equalsIgnoreCase(algorithm)) {
|
|
|
|
|
+ ECCurves curve = ECCurves.fromCurveSize(keySize);
|
|
|
|
|
+ if (curve == null) {
|
|
|
|
|
+ throw new InvalidKeySpecException("Unknown curve for key size=" + keySize);
|
|
|
|
|
+ }
|
|
|
|
|
+ gen.initialize(curve.getParameters());
|
|
|
|
|
+ } else {
|
|
|
|
|
+ gen.initialize(keySize);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return gen.generateKeyPair();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static KeyPairProvider createTestHostKeyProvider(Class<?> anchor) {
|
|
|
|
|
+ KeyPairProvider provider = KEYPAIR_PROVIDER_HOLDER.get();
|
|
|
|
|
+ if (provider != null) {
|
|
|
|
|
+ return provider;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Path targetFolder = Objects.requireNonNull(
|
|
|
|
|
+ CommonTestSupportUtils.detectTargetFolder(anchor), "Failed to detect target folder");
|
|
|
|
|
+ Path file = targetFolder.resolve("hostkey." + DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM.toLowerCase());
|
|
|
|
|
+ provider = createTestHostKeyProvider(file);
|
|
|
|
|
+
|
|
|
|
|
+ KeyPairProvider prev = KEYPAIR_PROVIDER_HOLDER.getAndSet(provider);
|
|
|
|
|
+ if (prev != null) { // check if somebody else beat us to it
|
|
|
|
|
+ return prev;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return provider;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static KeyPairProvider createTestHostKeyProvider(Path path) {
|
|
|
|
|
+ SimpleGeneratorHostKeyProvider keyProvider = new SimpleGeneratorHostKeyProvider();
|
|
|
|
|
+ keyProvider.setPath(Objects.requireNonNull(path, "No path"));
|
|
|
|
|
+ keyProvider.setAlgorithm(DEFAULT_TEST_HOST_KEY_PROVIDER_ALGORITHM);
|
|
|
|
|
+ keyProvider.setKeySize(DEFAULT_TEST_HOST_KEY_SIZE);
|
|
|
|
|
+ return validateKeyPairProvider(keyProvider);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static KeyPair getFirstKeyPair(KeyPairProviderHolder holder) {
|
|
|
|
|
+ return getFirstKeyPair(Objects.requireNonNull(holder, "No holder").getKeyPairProvider());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static KeyPair getFirstKeyPair(KeyIdentityProvider provider) {
|
|
|
|
|
+ Objects.requireNonNull(provider, "No key pair provider");
|
|
|
|
|
+ Iterable<KeyPair> pairs;
|
|
|
|
|
+ try {
|
|
|
|
|
+ pairs = Objects.requireNonNull(provider.loadKeys(null), "No loaded keys");
|
|
|
|
|
+ } catch (IOException | GeneralSecurityException e) {
|
|
|
|
|
+ throw new RuntimeException(
|
|
|
|
|
+ "Unexpected " + e.getClass().getSimpleName() + ")" + " keys loading exception: " + e.getMessage(),
|
|
|
|
|
+ e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Iterator<KeyPair> iter = Objects.requireNonNull(pairs.iterator(), "No keys iterator");
|
|
|
|
|
+ ValidateUtils.checkTrue(iter.hasNext(), "Empty loaded kyes iterator");
|
|
|
|
|
+ return Objects.requireNonNull(iter.next(), "No key pair in iterator");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private static Path getFile(String resource) {
|
|
|
|
|
+ URL url = CommonTestSupportUtils.class.getClassLoader().getResource(resource);
|
|
|
|
|
+ try {
|
|
|
|
|
+ return Paths.get(url.toURI());
|
|
|
|
|
+ } catch (URISyntaxException e) {
|
|
|
|
|
+ return Paths.get(url.getPath());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static FileKeyPairProvider createTestKeyPairProvider(String resource) {
|
|
|
|
|
+ Path file = getFile(resource);
|
|
|
|
|
+ file = file.toAbsolutePath();
|
|
|
|
|
+ String filePath = Objects.toString(file, "");
|
|
|
|
|
+ FileKeyPairProvider provider = PROVIDERS_MAP.get(filePath);
|
|
|
|
|
+ if (provider != null) {
|
|
|
|
|
+ return provider;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ provider = new FileKeyPairProvider();
|
|
|
|
|
+ provider.setPaths(Collections.singletonList(file));
|
|
|
|
|
+ provider = validateKeyPairProvider(provider);
|
|
|
|
|
+
|
|
|
|
|
+ FileKeyPairProvider prev = PROVIDERS_MAP.put(filePath, provider);
|
|
|
|
|
+ if (prev != null) { // check if somebody else beat us to it
|
|
|
|
|
+ return prev;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ return provider;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static <P extends KeyIdentityProvider> P validateKeyPairProvider(P provider) {
|
|
|
|
|
+ Objects.requireNonNull(provider, "No provider");
|
|
|
|
|
+
|
|
|
|
|
+ // get the I/O out of the way
|
|
|
|
|
+ Iterable<KeyPair> keys;
|
|
|
|
|
+ try {
|
|
|
|
|
+ keys = Objects.requireNonNull(provider.loadKeys(null), "No keys loaded");
|
|
|
|
|
+ } catch (IOException | GeneralSecurityException e) {
|
|
|
|
|
+ throw new RuntimeException(
|
|
|
|
|
+ "Unexpected " + e.getClass().getSimpleName() + ")" + " keys loading exception: " + e.getMessage(),
|
|
|
|
|
+ e);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (keys instanceof Collection<?>) {
|
|
|
|
|
+ ValidateUtils.checkNotNullAndNotEmpty((Collection<?>) keys, "Empty keys loaded");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return provider;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java
|
|
|
|
|
new file mode 100644
|
2025-09-25 17:16:17 +02:00
|
|
|
index 000000000..9ed5963a8
|
2025-09-22 17:56:49 +02:00
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/CoreTestSupportUtils.java
|
2025-09-25 17:16:17 +02:00
|
|
|
@@ -0,0 +1,91 @@
|
2025-09-22 17:56:49 +02:00
|
|
|
+/*
|
|
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
|
|
+ * distributed with this work for additional information
|
|
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+ *
|
|
|
|
|
+ * Unless required by applicable law or agreed to in writing,
|
|
|
|
|
+ * software distributed under the License is distributed on an
|
|
|
|
|
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
+ * KIND, either express or implied. See the License for the
|
|
|
|
|
+ * specific language governing permissions and limitations
|
|
|
|
|
+ * under the License.
|
|
|
|
|
+ */
|
|
|
|
|
+package org.apache.maven.scm.provider.git;
|
|
|
|
|
+
|
|
|
|
|
+import java.net.InetAddress;
|
|
|
|
|
+import java.net.InetSocketAddress;
|
|
|
|
|
+import java.net.ServerSocket;
|
|
|
|
|
+import java.time.Duration;
|
2025-09-25 17:16:17 +02:00
|
|
|
+import java.util.ArrayList;
|
|
|
|
|
+import java.util.List;
|
2025-09-22 17:56:49 +02:00
|
|
|
+
|
2025-09-25 17:16:17 +02:00
|
|
|
+import org.apache.sshd.common.NamedFactory;
|
|
|
|
|
+import org.apache.sshd.common.cipher.BuiltinCiphers;
|
|
|
|
|
+import org.apache.sshd.common.cipher.Cipher;
|
2025-09-22 17:56:49 +02:00
|
|
|
+import org.apache.sshd.common.util.GenericUtils;
|
|
|
|
|
+import org.apache.sshd.core.CoreModuleProperties;
|
|
|
|
|
+import org.apache.sshd.server.SshServer;
|
|
|
|
|
+import org.apache.sshd.server.auth.pubkey.AcceptAllPublickeyAuthenticator;
|
|
|
|
|
+import org.apache.sshd.server.shell.UnknownCommandFactory;
|
|
|
|
|
+
|
|
|
|
|
+public final class CoreTestSupportUtils {
|
|
|
|
|
+ public static final Duration READ_TIMEOUT = getTimeout("read.nio2", Duration.ofSeconds(60));
|
|
|
|
|
+
|
|
|
|
|
+ private CoreTestSupportUtils() {
|
|
|
|
|
+ throw new UnsupportedOperationException("No instance");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static int getFreePort() throws Exception {
|
|
|
|
|
+ try (ServerSocket s = new ServerSocket()) {
|
|
|
|
|
+ s.setReuseAddress(true);
|
|
|
|
|
+ s.bind(new InetSocketAddress((InetAddress) null, 0));
|
|
|
|
|
+ return s.getLocalPort();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static SshServer setupTestServer(Class<?> anchor) {
|
|
|
|
|
+ return setupTestServer(SshServer.setUpDefaultServer(), anchor);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static <S extends SshServer> S setupTestServer(S sshd, Class<?> anchor) {
|
2025-09-25 17:16:17 +02:00
|
|
|
+ List<NamedFactory<Cipher>> cipherFactories = new ArrayList<>(sshd.getCipherFactories());
|
|
|
|
|
+ cipherFactories.add(BuiltinCiphers.aes128cbc);
|
|
|
|
|
+ sshd.setCipherFactories(cipherFactories);
|
2025-09-22 17:56:49 +02:00
|
|
|
+ sshd.setKeyPairProvider(CommonTestSupportUtils.createTestHostKeyProvider(anchor));
|
|
|
|
|
+ sshd.setPasswordAuthenticator(BogusPasswordAuthenticator.INSTANCE);
|
|
|
|
|
+ sshd.setPublickeyAuthenticator(AcceptAllPublickeyAuthenticator.INSTANCE);
|
|
|
|
|
+ sshd.setShellFactory(EchoShellFactory.INSTANCE);
|
|
|
|
|
+ sshd.setCommandFactory(UnknownCommandFactory.INSTANCE);
|
|
|
|
|
+ CoreModuleProperties.NIO2_READ_TIMEOUT.set(sshd, READ_TIMEOUT);
|
|
|
|
|
+ return sshd;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public static Duration getTimeout(String property, Duration defaultValue) {
|
|
|
|
|
+ // Do we have a specific timeout value ?
|
|
|
|
|
+ String str = System.getProperty("org.apache.sshd.test.timeout." + property);
|
|
|
|
|
+ if (GenericUtils.isNotEmpty(str)) {
|
|
|
|
|
+ return Duration.ofMillis(Long.parseLong(str));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Do we have a specific factor ?
|
|
|
|
|
+ str = System.getProperty("org.apache.sshd.test.timeout.factor." + property);
|
|
|
|
|
+ if (GenericUtils.isEmpty(str)) {
|
|
|
|
|
+ // Do we have a global factor ?
|
|
|
|
|
+ str = System.getProperty("org.apache.sshd.test.timeout.factor");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (GenericUtils.isNotEmpty(str)) {
|
|
|
|
|
+ double factor = Double.parseDouble(str);
|
|
|
|
|
+ long dur = Math.round(defaultValue.toMillis() * factor);
|
|
|
|
|
+ return Duration.ofMillis(dur);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return defaultValue;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 000000000..65771a451
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShell.java
|
|
|
|
|
@@ -0,0 +1,40 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
|
|
+ * distributed with this work for additional information
|
|
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+ *
|
|
|
|
|
+ * Unless required by applicable law or agreed to in writing,
|
|
|
|
|
+ * software distributed under the License is distributed on an
|
|
|
|
|
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
+ * KIND, either express or implied. See the License for the
|
|
|
|
|
+ * specific language governing permissions and limitations
|
|
|
|
|
+ * under the License.
|
|
|
|
|
+ */
|
|
|
|
|
+package org.apache.maven.scm.provider.git;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.OutputStream;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
|
|
|
+ */
|
|
|
|
|
+public class EchoShell extends CommandExecutionHelper {
|
|
|
|
|
+ public EchoShell() {
|
|
|
|
|
+ super();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ protected boolean handleCommandLine(String command) throws Exception {
|
|
|
|
|
+ OutputStream out = getOutputStream();
|
|
|
|
|
+ out.write((command + "\n").getBytes(StandardCharsets.UTF_8));
|
|
|
|
|
+ out.flush();
|
|
|
|
|
+
|
|
|
|
|
+ return !"exit".equals(command);
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java
|
|
|
|
|
new file mode 100644
|
|
|
|
|
index 000000000..2a8b162ee
|
|
|
|
|
--- /dev/null
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/EchoShellFactory.java
|
|
|
|
|
@@ -0,0 +1,41 @@
|
|
|
|
|
+/*
|
|
|
|
|
+ * Licensed to the Apache Software Foundation (ASF) under one
|
|
|
|
|
+ * or more contributor license agreements. See the NOTICE file
|
|
|
|
|
+ * distributed with this work for additional information
|
|
|
|
|
+ * regarding copyright ownership. The ASF licenses this file
|
|
|
|
|
+ * to you under the Apache License, Version 2.0 (the
|
|
|
|
|
+ * "License"); you may not use this file except in compliance
|
|
|
|
|
+ * with the License. You may obtain a copy of the License at
|
|
|
|
|
+ *
|
|
|
|
|
+ * http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
+ *
|
|
|
|
|
+ * Unless required by applicable law or agreed to in writing,
|
|
|
|
|
+ * software distributed under the License is distributed on an
|
|
|
|
|
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
|
|
|
+ * KIND, either express or implied. See the License for the
|
|
|
|
|
+ * specific language governing permissions and limitations
|
|
|
|
|
+ * under the License.
|
|
|
|
|
+ */
|
|
|
|
|
+package org.apache.maven.scm.provider.git;
|
|
|
|
|
+
|
|
|
|
|
+import org.apache.sshd.server.channel.ChannelSession;
|
|
|
|
|
+import org.apache.sshd.server.command.Command;
|
|
|
|
|
+import org.apache.sshd.server.shell.ShellFactory;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * TODO Add javadoc
|
|
|
|
|
+ *
|
|
|
|
|
+ * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
|
|
|
|
|
+ */
|
|
|
|
|
+public class EchoShellFactory implements ShellFactory {
|
|
|
|
|
+ public static final EchoShellFactory INSTANCE = new EchoShellFactory();
|
|
|
|
|
+
|
|
|
|
|
+ public EchoShellFactory() {
|
|
|
|
|
+ super();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ @Override
|
|
|
|
|
+ public Command createShell(ChannelSession channel) {
|
|
|
|
|
+ return new EchoShell();
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
diff --git a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java
|
|
|
|
|
index a58525907..054990fd3 100644
|
|
|
|
|
--- a/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java
|
|
|
|
|
+++ b/maven-scm-providers/maven-scm-providers-git/maven-scm-provider-gittest/src/main/java/org/apache/maven/scm/provider/git/GitSshServer.java
|
|
|
|
|
@@ -40,8 +40,6 @@
|
|
|
|
|
import org.apache.sshd.server.auth.pubkey.KeySetPublickeyAuthenticator;
|
|
|
|
|
import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator;
|
|
|
|
|
import org.apache.sshd.server.session.ServerSession;
|
|
|
|
|
-import org.apache.sshd.util.test.CommonTestSupportUtils;
|
|
|
|
|
-import org.apache.sshd.util.test.CoreTestSupportUtils;
|
|
|
|
|
import org.bouncycastle.openssl.PKCS8Generator;
|
|
|
|
|
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
|
|
|
|
|
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
|
|
|
|
|
--
|
|
|
|
|
2.51.0
|
|
|
|
|
|