This is an automated email from the ASF dual-hosted git repository.
cstamas pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven.git
The following commit(s) were added to refs/heads/master by this push:
new f5e54ca6fa [MNG-8285] Implement mvnenc CLI tool (#1793)
f5e54ca6fa is described below
commit f5e54ca6faa5d3025beb202b59640947e42fc336
Author: Tamas Cservenak <[email protected]>
AuthorDate: Mon Oct 14 21:57:22 2024 +0200
[MNG-8285] Implement mvnenc CLI tool (#1793)
Implements the `mvnenc` tool that is on par with Maven3 master password
encryption functionality wise, but is _really secure_ unlike Maven3 conterpart.
On the other hand, _is backward compatible if legacy config is setup_.
Implemented goals: `init`, `encrypt`, `decrypt`, `diag`. Also provides one
extra "master source" based on Maven infra Prompter: console master password
prompt.
---
https://issues.apache.org/jira/browse/MNG-8285
---
apache-maven/src/assembly/component.xml | 1 +
apache-maven/src/assembly/maven/bin/mvnencDebug | 35 +++
.../src/assembly/maven/bin/mvnencDebug.cmd | 44 ++++
.../maven/api/cli/mvnenc/EncryptOptions.java | 22 +-
maven-cli/pom.xml | 4 +
.../java/org/apache/maven/cling/ClingSupport.java | 11 +-
.../java/org/apache/maven/cling/MavenCling.java | 12 +
.../java/org/apache/maven/cling/MavenEncCling.java | 23 +-
.../invoker/mvnenc/CommonsCliEncryptOptions.java | 41 ++-
.../invoker/mvnenc/ConsolePasswordPrompt.java | 80 ++++++
.../invoker/mvnenc/DefaultEncryptInvoker.java | 118 ++++++++-
.../apache/maven/cling/invoker/mvnenc/Goal.java | 26 ++
.../mvnenc/goals/ConfiguredGoalSupport.java | 94 +++++++
.../maven/cling/invoker/mvnenc/goals/Decrypt.java | 54 ++++
.../maven/cling/invoker/mvnenc/goals/Diag.java | 47 ++++
.../maven/cling/invoker/mvnenc/goals/Encrypt.java | 48 ++++
.../cling/invoker/mvnenc/goals/GoalSupport.java | 45 ++++
.../maven/cling/invoker/mvnenc/goals/Init.java | 275 +++++++++++++++++++++
.../DefaultRepositorySystemSessionFactory.java | 13 +-
maven-embedder/pom.xml | 4 -
maven-jline/pom.xml | 12 +
maven-settings-builder/pom.xml | 4 -
.../settings/crypto/DefaultSettingsDecrypter.java | 101 +++++---
.../maven/settings/crypto/MavenSecDispatcher.java | 58 +++++
pom.xml | 23 +-
25 files changed, 1079 insertions(+), 116 deletions(-)
diff --git a/apache-maven/src/assembly/component.xml
b/apache-maven/src/assembly/component.xml
index 347dee1d5c..9a6d61dc5c 100644
--- a/apache-maven/src/assembly/component.xml
+++ b/apache-maven/src/assembly/component.xml
@@ -85,6 +85,7 @@ under the License.
<include>mvn</include>
<include>mvnenc</include>
<include>mvnDebug</include>
+ <include>mvnencDebug</include>
<!-- This is so that CI systems can periodically run the profiler -->
<include>mvnyjp</include>
</includes>
diff --git a/apache-maven/src/assembly/maven/bin/mvnencDebug
b/apache-maven/src/assembly/maven/bin/mvnencDebug
new file mode 100644
index 0000000000..50b3e67492
--- /dev/null
+++ b/apache-maven/src/assembly/maven/bin/mvnencDebug
@@ -0,0 +1,35 @@
+#!/bin/sh
+
+# 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.
+
+# -----------------------------------------------------------------------------
+# Apache Maven Debug Script
+#
+# Environment Variable Prerequisites
+#
+# JAVA_HOME (Optional) Points to a Java installation.
+# MAVEN_OPTS (Optional) Java runtime options used when Maven is
executed.
+# MAVEN_SKIP_RC (Optional) Flag to disable loading of mavenrc files.
+# MAVEN_DEBUG_ADDRESS (Optional) Set the debug address. Default value is
localhost:8000
+# -----------------------------------------------------------------------------
+
+MAVEN_DEBUG_OPTS="-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=${MAVEN_DEBUG_ADDRESS:-localhost:8000}"
+
+echo Preparing to execute Maven in debug mode
+
+env MAVEN_OPTS="$MAVEN_OPTS" MAVEN_DEBUG_OPTS="$MAVEN_DEBUG_OPTS" "`dirname
"$0"`/mvnenc" "$@"
diff --git a/apache-maven/src/assembly/maven/bin/mvnencDebug.cmd
b/apache-maven/src/assembly/maven/bin/mvnencDebug.cmd
new file mode 100644
index 0000000000..22a869cd5b
--- /dev/null
+++ b/apache-maven/src/assembly/maven/bin/mvnencDebug.cmd
@@ -0,0 +1,44 @@
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM http://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+
+@REM
-----------------------------------------------------------------------------
+@REM Apache Maven Debug Script
+@REM
+@REM Environment Variable Prerequisites
+@REM
+@REM JAVA_HOME (Optional) Points to a Java installation.
+@REM MAVEN_BATCH_ECHO (Optional) Set to 'on' to enable the echoing of the
batch commands.
+@REM MAVEN_BATCH_PAUSE (Optional) set to 'on' to wait for a key stroke
before ending.
+@REM MAVEN_OPTS (Optional) Java runtime options used when Maven is
executed.
+@REM MAVEN_SKIP_RC (Optional) Flag to disable loading of mavenrc files.
+@REM MAVEN_DEBUG_ADDRESS (Optional) Set the debug address. Default value is
localhost:8000
+@REM
-----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%"=="on" echo %MAVEN_BATCH_ECHO%
+
+@setlocal
+
+if "%MAVEN_DEBUG_ADDRESS%"=="" @set MAVEN_DEBUG_ADDRESS=localhost:8000
+
+@set MAVEN_DEBUG_OPTS=-Xdebug
-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=%MAVEN_DEBUG_ADDRESS%
+
+@call "%~dp0"mvnenc.cmd %*
diff --git
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnenc/EncryptOptions.java
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnenc/EncryptOptions.java
index a70e856e3a..910d0375ea 100644
---
a/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnenc/EncryptOptions.java
+++
b/api/maven-api-cli/src/main/java/org/apache/maven/api/cli/mvnenc/EncryptOptions.java
@@ -36,28 +36,18 @@ import org.apache.maven.api.cli.Options;
@Experimental
public interface EncryptOptions extends Options {
/**
- * Returns the cipher that the user wants to use for non-dispatched
encryption.
+ * Should the operation be forced (ie overwrite existing config, if any).
*
- * @return an {@link Optional} containing the cipher string, or empty if
not specified
+ * @return an {@link Optional} containing the boolean value {@code true}
if specified, or empty
*/
- @Nonnull
- Optional<String> cipher();
+ Optional<Boolean> force();
/**
- * Returns the master source that the user wants to use for non-dispatched
encryption.
+ * Should imply "yes" to all questions.
*
- * @return an {@link Optional} containing the master source string, or
empty if not specified
+ * @return an {@link Optional} containing the boolean value {@code true}
if specified, or empty
*/
- @Nonnull
- Optional<String> masterSource();
-
- /**
- * Returns the dispatcher to use for dispatched encryption.
- *
- * @return an {@link Optional} containing the dispatcher string, or empty
if not specified
- */
- @Nonnull
- Optional<String> dispatcher();
+ Optional<Boolean> yes();
/**
* Returns the list of encryption goals to be executed.
diff --git a/maven-cli/pom.xml b/maven-cli/pom.xml
index 1ce18d63d5..8c79d44942 100644
--- a/maven-cli/pom.xml
+++ b/maven-cli/pom.xml
@@ -92,6 +92,10 @@ under the License.
<build>
<plugins>
+ <plugin>
+ <groupId>org.eclipse.sisu</groupId>
+ <artifactId>sisu-maven-plugin</artifactId>
+ </plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
b/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
index 5ea9ec72db..ba65331388 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/ClingSupport.java
@@ -25,7 +25,6 @@ import org.apache.maven.api.cli.InvokerException;
import org.apache.maven.api.cli.InvokerRequest;
import org.apache.maven.api.cli.Options;
import org.apache.maven.api.cli.ParserException;
-import org.apache.maven.jline.MessageUtils;
import org.codehaus.plexus.classworlds.ClassWorld;
import static java.util.Objects.requireNonNull;
@@ -65,8 +64,6 @@ public abstract class ClingSupport<O extends Options, R
extends InvokerRequest<O
* The main entry point.
*/
public int run(String[] args) throws IOException {
- MessageUtils.systemInstall();
- MessageUtils.registerShutdownHook();
try (Invoker<R> invoker = createInvoker()) {
return invoker.invoke(parseArguments(args));
} catch (ParserException e) {
@@ -75,12 +72,8 @@ public abstract class ClingSupport<O extends Options, R
extends InvokerRequest<O
} catch (InvokerException e) {
return 1;
} finally {
- try {
- if (classWorldManaged) {
- classWorld.close();
- }
- } finally {
- MessageUtils.systemUninstall();
+ if (classWorldManaged) {
+ classWorld.close();
}
}
}
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
b/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
index b8b204d5f4..691f207fb5 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/MavenCling.java
@@ -29,6 +29,7 @@ import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvn.DefaultMavenParser;
import org.apache.maven.cling.invoker.mvn.local.DefaultLocalMavenInvoker;
import org.apache.maven.jline.JLineMessageBuilderFactory;
+import org.apache.maven.jline.MessageUtils;
import org.codehaus.plexus.classworlds.ClassWorld;
/**
@@ -59,6 +60,17 @@ public class MavenCling extends ClingSupport<MavenOptions,
MavenInvokerRequest<M
super(classWorld);
}
+ @Override
+ public int run(String[] args) throws IOException {
+ MessageUtils.systemInstall();
+ MessageUtils.registerShutdownHook();
+ try {
+ return super.run(args);
+ } finally {
+ MessageUtils.systemUninstall();
+ }
+ }
+
@Override
protected Invoker<MavenInvokerRequest<MavenOptions>> createInvoker() {
return new DefaultLocalMavenInvoker(
diff --git a/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
b/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
index 488eb9eae4..72cb51ea2c 100644
--- a/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
+++ b/maven-cli/src/main/java/org/apache/maven/cling/MavenEncCling.java
@@ -30,7 +30,10 @@ import org.apache.maven.cling.invoker.ProtoLookup;
import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptParser;
import org.apache.maven.jline.JLineMessageBuilderFactory;
+import org.apache.maven.jline.MessageUtils;
import org.codehaus.plexus.classworlds.ClassWorld;
+import org.jline.terminal.Terminal;
+import org.jline.terminal.TerminalBuilder;
/**
* Maven encrypt CLI "new-gen".
@@ -52,6 +55,8 @@ public class MavenEncCling extends
ClingSupport<EncryptOptions, EncryptInvokerRe
return new MavenEncCling(world).run(args);
}
+ private Terminal terminal;
+
public MavenEncCling() {
super();
}
@@ -60,10 +65,24 @@ public class MavenEncCling extends
ClingSupport<EncryptOptions, EncryptInvokerRe
super(classWorld);
}
+ @Override
+ public int run(String[] args) throws IOException {
+ terminal = TerminalBuilder.builder().build();
+ MessageUtils.systemInstall(terminal);
+ MessageUtils.registerShutdownHook();
+ try {
+ return super.run(args);
+ } finally {
+ MessageUtils.systemUninstall();
+ }
+ }
+
@Override
protected Invoker<EncryptInvokerRequest> createInvoker() {
- return new DefaultEncryptInvoker(
- ProtoLookup.builder().addMapping(ClassWorld.class,
classWorld).build());
+ return new DefaultEncryptInvoker(ProtoLookup.builder()
+ .addMapping(ClassWorld.class, classWorld)
+ .addMapping(Terminal.class, terminal)
+ .build());
}
@Override
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
index c1fdb3e76a..fcf964fee8 100644
---
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/CommonsCliEncryptOptions.java
@@ -72,25 +72,17 @@ public class CommonsCliEncryptOptions extends
CommonsCliOptions implements Encry
}
@Override
- public Optional<String> cipher() {
- if (commandLine.hasOption(CLIManager.CIPHER)) {
- return Optional.of(commandLine.getOptionValue(CLIManager.CIPHER));
+ public Optional<Boolean> force() {
+ if (commandLine.hasOption(CLIManager.FORCE)) {
+ return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@Override
- public Optional<String> masterSource() {
- if (commandLine.hasOption(CLIManager.MASTER_SOURCE)) {
- return
Optional.of(commandLine.getOptionValue(CLIManager.MASTER_SOURCE));
- }
- return Optional.empty();
- }
-
- @Override
- public Optional<String> dispatcher() {
- if (commandLine.hasOption(CLIManager.DISPATCHER)) {
- return
Optional.of(commandLine.getOptionValue(CLIManager.DISPATCHER));
+ public Optional<Boolean> yes() {
+ if (commandLine.hasOption(CLIManager.YES)) {
+ return Optional.of(Boolean.TRUE);
}
return Optional.empty();
}
@@ -109,24 +101,19 @@ public class CommonsCliEncryptOptions extends
CommonsCliOptions implements Encry
}
protected static class CLIManager extends CommonsCliOptions.CLIManager {
- public static final String CIPHER = "c";
- public static final String MASTER_SOURCE = "m";
- public static final String DISPATCHER = "d";
+ public static final String FORCE = "f";
+ public static final String YES = "y";
@Override
protected void prepareOptions(org.apache.commons.cli.Options options) {
super.prepareOptions(options);
- options.addOption(Option.builder(CIPHER)
- .longOpt("cipher")
- .desc("The cipher that user wants to use for
non-dispatched encryption")
- .build());
- options.addOption(Option.builder(MASTER_SOURCE)
- .longOpt("master-source")
- .desc("The master source that user wants to use for
non-dispatched encryption")
+ options.addOption(Option.builder(FORCE)
+ .longOpt("force")
+ .desc("Should overwrite without asking any configuration?")
.build());
- options.addOption(Option.builder(DISPATCHER)
- .longOpt("dispatcher")
- .desc("The dispatcher to use for dispatched encryption")
+ options.addOption(Option.builder(YES)
+ .longOpt("yes")
+ .desc("Should imply user answered \"yes\" to all incoming
questions?")
.build());
}
}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/ConsolePasswordPrompt.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/ConsolePasswordPrompt.java
new file mode 100644
index 0000000000..a3981a7897
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/ConsolePasswordPrompt.java
@@ -0,0 +1,80 @@
+/*
+ * 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.cling.invoker.mvnenc;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.apache.maven.api.services.Prompter;
+import org.apache.maven.api.services.PrompterException;
+import org.codehaus.plexus.components.secdispatcher.MasterSource;
+import org.codehaus.plexus.components.secdispatcher.MasterSourceMeta;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcherException;
+
+/**
+ * Trivial master password source using Maven {@link Prompter} service.
+ */
+@Singleton
+@Named(ConsolePasswordPrompt.NAME)
+public class ConsolePasswordPrompt implements MasterSource, MasterSourceMeta {
+ public static final String NAME = "console-prompt";
+
+ private final Prompter prompter;
+
+ @Inject
+ public ConsolePasswordPrompt(Prompter prompter) {
+ this.prompter = prompter;
+ }
+
+ @Override
+ public String description() {
+ return "Secure console password prompt";
+ }
+
+ @Override
+ public Optional<String> configTemplate() {
+ return Optional.empty();
+ }
+
+ @Override
+ public String handle(String config) throws SecDispatcherException {
+ if (NAME.equals(config)) {
+ try {
+ return prompter.promptForPassword("Enter the master password:
");
+ } catch (PrompterException e) {
+ throw new SecDispatcherException("Could not collect the
password", e);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public SecDispatcher.ValidationResponse validateConfiguration(String
config) {
+ if (NAME.equals(config)) {
+ return new
SecDispatcher.ValidationResponse(getClass().getSimpleName(), true, Map.of(),
List.of());
+ }
+ return null;
+ }
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java
index ff0cb4d05b..82566bbe3f 100644
---
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/DefaultEncryptInvoker.java
@@ -18,12 +18,27 @@
*/
package org.apache.maven.cling.invoker.mvnenc;
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
import org.apache.maven.api.cli.mvnenc.EncryptInvoker;
import org.apache.maven.api.cli.mvnenc.EncryptInvokerRequest;
import org.apache.maven.api.cli.mvnenc.EncryptOptions;
+import org.apache.maven.cli.CLIReportingUtils;
import org.apache.maven.cling.invoker.LookupInvoker;
import org.apache.maven.cling.invoker.ProtoLookup;
-import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+import org.jline.consoleui.prompt.ConsolePrompt;
+import org.jline.reader.LineReader;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.reader.UserInterruptException;
+import org.jline.terminal.Terminal;
+import org.jline.utils.AttributedString;
+import org.jline.utils.AttributedStringBuilder;
+import org.jline.utils.AttributedStyle;
+import org.jline.utils.Colors;
+import org.jline.utils.OSUtils;
/**
* Encrypt invoker implementation, when Encrypt CLI is being run. System uses
ClassWorld launcher, and class world
@@ -33,17 +48,36 @@ public class DefaultEncryptInvoker
extends LookupInvoker<EncryptOptions, EncryptInvokerRequest,
DefaultEncryptInvoker.LocalContext>
implements EncryptInvoker {
+ @SuppressWarnings("VisibilityModifier")
public static class LocalContext
extends LookupInvokerContext<EncryptOptions,
EncryptInvokerRequest, DefaultEncryptInvoker.LocalContext> {
protected LocalContext(DefaultEncryptInvoker invoker,
EncryptInvokerRequest invokerRequest) {
super(invoker, invokerRequest);
}
- protected SecDispatcher secDispatcher;
+ public Map<String, Goal> goals;
+
+ public List<AttributedString> header;
+ public AttributedStyle style;
+ public LineReader reader;
+ public ConsolePrompt prompt;
+
+ public void addInHeader(String text) {
+ addInHeader(AttributedStyle.DEFAULT, text);
+ }
+
+ public void addInHeader(AttributedStyle style, String text) {
+ AttributedStringBuilder asb = new AttributedStringBuilder();
+ asb.style(style).append(text);
+ header.add(asb.toAttributedString());
+ }
}
+ private final Terminal terminal;
+
public DefaultEncryptInvoker(ProtoLookup protoLookup) {
super(protoLookup);
+ this.terminal = protoLookup.lookup(Terminal.class);
}
@Override
@@ -58,14 +92,80 @@ public class DefaultEncryptInvoker
@Override
protected void lookup(LocalContext context) {
- context.secDispatcher = context.lookup.lookup(SecDispatcher.class);
+ context.goals = context.lookup.lookupMap(Goal.class);
+ }
+
+ public static final int OK = 0; // OK
+ public static final int ERROR = 1; // "generic" error
+ public static final int BAD_OPERATION = 2; // bad user input or alike
+ public static final int CANCELED = 3; // user canceled
+
+ protected int doExecute(LocalContext context) throws Exception {
+ if (!context.interactive) {
+ System.out.println("This tool works only in interactive mode!");
+ System.out.println("Tool purpose is to configure password
management on developer workstations.");
+ System.out.println(
+ "Note: Generated configuration can be moved/copied to
headless environments, if configured as such.");
+ return BAD_OPERATION;
+ }
+
+ context.header = new ArrayList<>();
+ context.style = new AttributedStyle();
+ context.addInHeader(
+
context.style.italic().bold().foreground(Colors.rgbColor("green")),
+ "Maven Encryption " + CLIReportingUtils.showVersionMinimal());
+ context.addInHeader("Tool for secure password management on
workstations.");
+ context.addInHeader("This tool is part of Apache Maven 4
distribution.");
+ context.addInHeader("");
+ try {
+ Thread executeThread = Thread.currentThread();
+ terminal.handle(Terminal.Signal.INT, signal ->
executeThread.interrupt());
+ ConsolePrompt.UiConfig config;
+ if (terminal.getType().equals(Terminal.TYPE_DUMB)
+ || terminal.getType().equals(Terminal.TYPE_DUMB_COLOR)) {
+ System.out.println(terminal.getName() + ": " +
terminal.getType());
+ throw new IllegalStateException("Dumb terminal detected.\nThis
tool requires real terminal to work!\n"
+ + "Note: On Windows Jansi or JNA library must be
included in classpath.");
+ } else if (OSUtils.IS_WINDOWS) {
+ config = new ConsolePrompt.UiConfig(">", "( )", "(x)", "( )");
+ } else {
+ config = new ConsolePrompt.UiConfig("❯", "◯ ", "◉ ", "◯ ");
+ }
+ config.setCancellableFirstPrompt(true);
+
+ context.reader =
LineReaderBuilder.builder().terminal(terminal).build();
+ context.prompt = new ConsolePrompt(context.reader, terminal,
config);
+
+ if (context.invokerRequest.options().goals().isEmpty()
+ || context.invokerRequest.options().goals().get().size()
!= 1) {
+ return badGoalsErrorMessage("No goal or multiple goals
specified, specify only one goal.", context);
+ }
+
+ String goalName =
context.invokerRequest.options().goals().get().get(0);
+ Goal goal = context.goals.get(goalName);
+
+ if (goal == null) {
+ return badGoalsErrorMessage("Unknown goal: " + goalName,
context);
+ }
+
+ return goal.execute(context);
+ } catch (InterruptedException | InterruptedIOException |
UserInterruptException e) {
+ System.out.println("Goal canceled by user.");
+ return CANCELED;
+ } catch (Exception e) {
+ if (context.invokerRequest.options().showErrors().orElse(false)) {
+ context.logger.error(e.getMessage(), e);
+ } else {
+ context.logger.error(e.getMessage());
+ }
+ return ERROR;
+ }
}
- protected int doExecute(LocalContext localContext) throws Exception {
- localContext.logger.info("Hello, this is SecDispatcher.");
- localContext.logger.info("Available Ciphers: " +
localContext.secDispatcher.availableCiphers());
- localContext.logger.info("Available Dispatchers: " +
localContext.secDispatcher.availableDispatchers());
- // TODO: implement mvnenc
- return 0;
+ protected int badGoalsErrorMessage(String message, LocalContext context) {
+ System.out.println(message);
+ System.out.println("Supported goals are: " + String.join(", ",
context.goals.keySet()));
+ System.out.println("Use -h to display help.");
+ return BAD_OPERATION;
}
}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/Goal.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/Goal.java
new file mode 100644
index 0000000000..d493b289e3
--- /dev/null
+++ b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/Goal.java
@@ -0,0 +1,26 @@
+/*
+ * 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.cling.invoker.mvnenc;
+
+/**
+ * The mvnenc tool goal.
+ */
+public interface Goal {
+ int execute(DefaultEncryptInvoker.LocalContext context) throws Exception;
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/ConfiguredGoalSupport.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/ConfiguredGoalSupport.java
new file mode 100644
index 0000000000..5719a816fc
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/ConfiguredGoalSupport.java
@@ -0,0 +1,94 @@
+/*
+ * 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.cling.invoker.mvnenc.goals;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+
+import static
org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.ERROR;
+
+/**
+ * The support class for goal implementations that requires valid/workable
config.
+ */
+public abstract class ConfiguredGoalSupport extends GoalSupport {
+ protected ConfiguredGoalSupport(MessageBuilderFactory
messageBuilderFactory, SecDispatcher secDispatcher) {
+ super(messageBuilderFactory, secDispatcher);
+ }
+
+ @Override
+ public int execute(DefaultEncryptInvoker.LocalContext context) throws
Exception {
+ if (!validateConfiguration()) {
+ logger.error(messageBuilderFactory
+ .builder()
+ .error("Maven Encryption is not configured, run `mvnenc
init` first.")
+ .build());
+ return ERROR;
+ }
+ return doExecute(context);
+ }
+
+ protected boolean validateConfiguration() {
+ SecDispatcher.ValidationResponse response =
secDispatcher.validateConfiguration();
+ if (!response.isValid() || logger.isDebugEnabled()) {
+ dumpResponse("", response);
+ }
+ return response.isValid();
+ }
+
+ protected void dumpResponse(String indent,
SecDispatcher.ValidationResponse response) {
+ logger.info(
+ response.isValid()
+ ? messageBuilderFactory
+ .builder()
+ .success("{}Configuration validation of {}:
{}")
+ .build()
+ : messageBuilderFactory
+ .builder()
+ .failure("{}Configuration validation of {}:
{}")
+ .build(),
+ indent,
+ response.getSource(),
+ response.isValid() ? "VALID" : "INVALID");
+ for (Map.Entry<SecDispatcher.ValidationResponse.Level, List<String>>
entry :
+ response.getReport().entrySet()) {
+ Consumer<String> consumer =
+ s ->
logger.info(messageBuilderFactory.builder().info(s).build());
+ if (entry.getKey() ==
SecDispatcher.ValidationResponse.Level.ERROR) {
+ consumer = s ->
+
logger.error(messageBuilderFactory.builder().error(s).build());
+ } else if (entry.getKey() ==
SecDispatcher.ValidationResponse.Level.WARNING) {
+ consumer = s ->
+
logger.warn(messageBuilderFactory.builder().warning(s).build());
+ }
+ for (String line : entry.getValue()) {
+ consumer.accept(indent + " " + line);
+ }
+ }
+ for (SecDispatcher.ValidationResponse subsystem :
response.getSubsystems()) {
+ dumpResponse(indent + " ", subsystem);
+ }
+ }
+
+ protected abstract int doExecute(DefaultEncryptInvoker.LocalContext
context) throws Exception;
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java
new file mode 100644
index 0000000000..6c437c9676
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Decrypt.java
@@ -0,0 +1,54 @@
+/*
+ * 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.cling.invoker.mvnenc.goals;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+
+import static
org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.BAD_OPERATION;
+import static org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.OK;
+
+/**
+ * The "decrypt" goal.
+ */
+@Singleton
+@Named("decrypt")
+public class Decrypt extends ConfiguredGoalSupport {
+ @Inject
+ public Decrypt(MessageBuilderFactory messageBuilderFactory, SecDispatcher
secDispatcher) {
+ super(messageBuilderFactory, secDispatcher);
+ }
+
+ @Override
+ protected int doExecute(DefaultEncryptInvoker.LocalContext context) throws
Exception {
+ String encrypted = context.reader.readLine("Enter the password to
decrypt: ");
+ if (secDispatcher.isAnyEncryptedString(encrypted)) {
+ logger.info(secDispatcher.decrypt(encrypted));
+ return OK;
+ } else {
+ logger.error("Malformed encrypted string");
+ return BAD_OPERATION;
+ }
+ }
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.java
new file mode 100644
index 0000000000..01d4680ac9
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Diag.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.cling.invoker.mvnenc.goals;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+
+import static org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.OK;
+
+/**
+ * The "diag" goal.
+ */
+@Singleton
+@Named("diag")
+public class Diag extends ConfiguredGoalSupport {
+ @Inject
+ public Diag(MessageBuilderFactory messageBuilderFactory, SecDispatcher
secDispatcher) {
+ super(messageBuilderFactory, secDispatcher);
+ }
+
+ @Override
+ protected int doExecute(DefaultEncryptInvoker.LocalContext context) {
+ dumpResponse("", secDispatcher.validateConfiguration());
+ return OK;
+ }
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java
new file mode 100644
index 0000000000..c17a5bd53f
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Encrypt.java
@@ -0,0 +1,48 @@
+/*
+ * 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.cling.invoker.mvnenc.goals;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+
+import static org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.OK;
+
+/**
+ * The "encrypt" goal.
+ */
+@Singleton
+@Named("encrypt")
+public class Encrypt extends ConfiguredGoalSupport {
+ @Inject
+ public Encrypt(MessageBuilderFactory messageBuilderFactory, SecDispatcher
secDispatcher) {
+ super(messageBuilderFactory, secDispatcher);
+ }
+
+ @Override
+ protected int doExecute(DefaultEncryptInvoker.LocalContext context) throws
Exception {
+ String cleartext = context.reader.readLine("Enter the password to
encrypt: ", '*');
+ logger.info(secDispatcher.encrypt(cleartext, null));
+ return OK;
+ }
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/GoalSupport.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/GoalSupport.java
new file mode 100644
index 0000000000..8a8b43ebb9
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/GoalSupport.java
@@ -0,0 +1,45 @@
+/*
+ * 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.cling.invoker.mvnenc.goals;
+
+import java.io.IOException;
+
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvnenc.Goal;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The support class for goal implementations.
+ */
+public abstract class GoalSupport implements Goal {
+ protected final Logger logger = LoggerFactory.getLogger(getClass());
+ protected final MessageBuilderFactory messageBuilderFactory;
+ protected final SecDispatcher secDispatcher;
+
+ protected GoalSupport(MessageBuilderFactory messageBuilderFactory,
SecDispatcher secDispatcher) {
+ this.messageBuilderFactory = messageBuilderFactory;
+ this.secDispatcher = secDispatcher;
+ }
+
+ protected boolean configExists() throws IOException {
+ return secDispatcher.readConfiguration(false) != null;
+ }
+}
diff --git
a/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java
new file mode 100644
index 0000000000..cd21e4abc2
--- /dev/null
+++
b/maven-cli/src/main/java/org/apache/maven/cling/invoker/mvnenc/goals/Init.java
@@ -0,0 +1,275 @@
+/*
+ * 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.cling.invoker.mvnenc.goals;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.maven.api.services.MessageBuilderFactory;
+import org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker;
+import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+import org.codehaus.plexus.components.secdispatcher.model.Config;
+import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty;
+import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
+import org.jline.consoleui.elements.ConfirmChoice;
+import org.jline.consoleui.prompt.ConfirmResult;
+import org.jline.consoleui.prompt.ConsolePrompt;
+import org.jline.consoleui.prompt.PromptResultItemIF;
+import org.jline.consoleui.prompt.builder.ListPromptBuilder;
+import org.jline.consoleui.prompt.builder.PromptBuilder;
+import org.jline.reader.Candidate;
+import org.jline.reader.Completer;
+import org.jline.reader.LineReader;
+import org.jline.reader.ParsedLine;
+import org.jline.utils.Colors;
+
+import static
org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.BAD_OPERATION;
+import static org.apache.maven.cling.invoker.mvnenc.DefaultEncryptInvoker.OK;
+
+/**
+ * The "init" goal.
+ */
+@Singleton
+@Named("init")
+public class Init extends GoalSupport {
+ private static final String NONE = "__none__";
+
+ @Inject
+ public Init(MessageBuilderFactory messageBuilderFactory, SecDispatcher
secDispatcher) {
+ super(messageBuilderFactory, secDispatcher);
+ }
+
+ @Override
+ public int execute(DefaultEncryptInvoker.LocalContext context) throws
Exception {
+
context.addInHeader(context.style.italic().bold().foreground(Colors.rgbColor("yellow")),
"goal: init");
+ context.addInHeader("");
+
+ ConsolePrompt prompt = context.prompt;
+ boolean force = context.invokerRequest.options().force().orElse(false);
+ boolean yes = context.invokerRequest.options().yes().orElse(false);
+
+ if (configExists() && !force) {
+ System.out.println(messageBuilderFactory
+ .builder()
+ .error("Error: configuration exist. Use --force if you
want to reset existing configuration."));
+ return BAD_OPERATION;
+ }
+
+ SettingsSecurity config = secDispatcher.readConfiguration(true);
+
+ // reset config
+ config.setDefaultDispatcher(null);
+ config.getConfigurations().clear();
+
+ Map<String, PromptResultItemIF> result = prompt.prompt(
+ context.header,
dispatcherPrompt(prompt.getPromptBuilder()).build());
+ if (result == null) {
+ throw new InterruptedException();
+ }
+ if (NONE.equals(result.get("defaultDispatcher").getResult())) {
+ logger.warn(messageBuilderFactory
+ .builder()
+ .warning(
+ "Maven4 SecDispatcher disabled; Maven3 fallback
may still work, use `mvnenc diag` to check")
+ .build());
+ secDispatcher.writeConfiguration(config);
+ return OK;
+ }
+
config.setDefaultDispatcher(result.get("defaultDispatcher").getResult());
+
+ DispatcherMeta meta = secDispatcher.availableDispatchers().stream()
+ .filter(d -> Objects.equals(config.getDefaultDispatcher(),
d.name()))
+ .findFirst()
+ .orElseThrow();
+ if (!meta.fields().isEmpty()) {
+ result = prompt.prompt(
+ context.header,
+ configureDispatcher(context, meta,
prompt.getPromptBuilder())
+ .build());
+ if (result == null) {
+ throw new InterruptedException();
+ }
+
+ List<Map.Entry<String, PromptResultItemIF>> editables =
result.entrySet().stream()
+ .filter(e -> e.getValue().getResult().contains("$"))
+ .toList();
+ if (!editables.isEmpty()) {
+ context.addInHeader("");
+ context.addInHeader("Please customize the editable value:");
+ Map<String, PromptResultItemIF> editMap;
+ for (Map.Entry<String, PromptResultItemIF> editable :
editables) {
+ String template = editable.getValue().getResult();
+ String prefix = template.substring(0,
template.indexOf("$"));
+ editMap = prompt.prompt(
+ context.header,
+ prompt.getPromptBuilder()
+ .createInputPrompt()
+ .name("edit")
+ .message(template)
+ .addCompleter(new Completer() {
+ @Override
+ public void complete(
+ LineReader reader, ParsedLine
line, List<Candidate> candidates) {
+ if
(!line.line().startsWith(prefix)) {
+ candidates.add(
+ new Candidate(prefix,
prefix, null, null, null, null, false));
+ }
+ }
+ })
+ .addPrompt()
+ .build());
+ if (editMap == null) {
+ throw new InterruptedException();
+ }
+ result.put(editable.getKey(), editMap.get("edit"));
+ }
+ }
+
+ Config dispatcherConfig = new Config();
+ dispatcherConfig.setName(meta.name());
+ for (DispatcherMeta.Field field : meta.fields()) {
+ ConfigProperty property = new ConfigProperty();
+ property.setName(field.getKey());
+ property.setValue(result.get(field.getKey()).getResult());
+ dispatcherConfig.addProperty(property);
+ }
+ if (!dispatcherConfig.getProperties().isEmpty()) {
+ config.addConfiguration(dispatcherConfig);
+ }
+ }
+
+ if (yes) {
+ secDispatcher.writeConfiguration(config);
+ } else {
+ context.addInHeader("");
+ context.addInHeader("Values set:");
+ context.addInHeader("defaultDispatcher=" +
config.getDefaultDispatcher());
+ for (Config c : config.getConfigurations()) {
+ context.addInHeader(" dispatcherName=" + c.getName());
+ for (ConfigProperty cp : c.getProperties()) {
+ context.addInHeader(" " + cp.getName() + "=" +
cp.getValue());
+ }
+ }
+
+ result = prompt.prompt(
+ context.header,
confirmPrompt(prompt.getPromptBuilder()).build());
+ ConfirmResult confirm = (ConfirmResult) result.get("confirm");
+ if (confirm.getConfirmed() == ConfirmChoice.ConfirmationValue.YES)
{
+ logger.info(messageBuilderFactory
+ .builder()
+ .info("Writing out the configuration...")
+ .build());
+ secDispatcher.writeConfiguration(config);
+ } else {
+ logger.warn(messageBuilderFactory
+ .builder()
+ .warning("Values not accepted; not saving
configuration.")
+ .build());
+ return BAD_OPERATION;
+ }
+ }
+
+ return OK;
+ }
+
+ protected PromptBuilder confirmPrompt(PromptBuilder promptBuilder) {
+ promptBuilder
+ .createConfirmPromp()
+ .name("confirm")
+ .message("Are values above correct?")
+ .defaultValue(ConfirmChoice.ConfirmationValue.YES)
+ .addPrompt();
+ return promptBuilder;
+ }
+
+ protected PromptBuilder dispatcherPrompt(PromptBuilder promptBuilder) {
+ ListPromptBuilder listPromptBuilder = promptBuilder
+ .createListPrompt()
+ .name("defaultDispatcher")
+ .message("Which dispatcher you want to use as default?");
+ listPromptBuilder
+ .newItem()
+ .name(NONE)
+ .text("None (disable MavenSecDispatcher)")
+ .add();
+ for (DispatcherMeta meta : secDispatcher.availableDispatchers()) {
+ if (!meta.isHidden()) {
+ listPromptBuilder
+ .newItem()
+ .name(meta.name())
+ .text(meta.displayName())
+ .add();
+ }
+ }
+ listPromptBuilder.addPrompt();
+ return promptBuilder;
+ }
+
+ private PromptBuilder configureDispatcher(
+ DefaultEncryptInvoker.LocalContext context, DispatcherMeta
dispatcherMeta, PromptBuilder promptBuilder)
+ throws Exception {
+ context.addInHeader(
+
context.style.italic().bold().foreground(Colors.rgbColor("yellow")),
+ "Configure " + dispatcherMeta.displayName());
+ context.addInHeader("");
+
+ for (DispatcherMeta.Field field : dispatcherMeta.fields()) {
+ String fieldKey = field.getKey();
+ String fieldDescription = "Configure " + fieldKey + ": " +
field.getDescription();
+ if (field.getOptions().isPresent()) {
+ // list options
+ ListPromptBuilder listPromptBuilder =
+
promptBuilder.createListPrompt().name(fieldKey).message(fieldDescription);
+ for (DispatcherMeta.Field option : field.getOptions().get()) {
+ listPromptBuilder
+ .newItem()
+ .name(
+ option.getDefaultValue().isPresent()
+ ? option.getDefaultValue().get()
+ : option.getKey())
+ .text(option.getDescription())
+ .add();
+ }
+ listPromptBuilder.addPrompt();
+ } else if (field.getDefaultValue().isPresent()) {
+ // input w/ def value
+ promptBuilder
+ .createInputPrompt()
+ .name(fieldKey)
+ .message(fieldDescription)
+ .defaultValue(field.getDefaultValue().get())
+ .addPrompt();
+ } else {
+ // ? plain input?
+ promptBuilder
+ .createInputPrompt()
+ .name(fieldKey)
+ .message(fieldDescription)
+ .addPrompt();
+ }
+ }
+ return promptBuilder;
+ }
+}
diff --git
a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
index f49ef3b727..9c214a5da4 100644
---
a/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
+++
b/maven-core/src/main/java/org/apache/maven/internal/aether/DefaultRepositorySystemSessionFactory.java
@@ -214,10 +214,15 @@ public class DefaultRepositorySystemSessionFactory
implements RepositorySystemSe
decrypt.setProxies(request.getProxies());
decrypt.setServers(request.getServers());
SettingsDecryptionResult decrypted =
settingsDecrypter.decrypt(decrypt);
-
- if (logger.isDebugEnabled()) {
- for (SettingsProblem problem : decrypted.getProblems()) {
- logger.debug(problem.getMessage(), problem.getException());
+ for (SettingsProblem problem : decrypted.getProblems()) {
+ if (problem.getSeverity() == SettingsProblem.Severity.WARNING) {
+ logger.warn(problem.getMessage());
+ } else if (problem.getSeverity() ==
SettingsProblem.Severity.ERROR) {
+ logger.error(
+ problem.getMessage(),
+ request.isShowErrors()
+ ? problem.getException()
+ : problem.getException().getMessage());
}
}
diff --git a/maven-embedder/pom.xml b/maven-embedder/pom.xml
index a5fb2351e0..5a36cf9fea 100644
--- a/maven-embedder/pom.xml
+++ b/maven-embedder/pom.xml
@@ -88,10 +88,6 @@ under the License.
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-sec-dispatcher</artifactId>
</dependency>
- <dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-cipher</artifactId>
- </dependency>
<dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-interpolation</artifactId>
diff --git a/maven-jline/pom.xml b/maven-jline/pom.xml
index 867bc34b41..09eb58de33 100644
--- a/maven-jline/pom.xml
+++ b/maven-jline/pom.xml
@@ -42,6 +42,18 @@ under the License.
<groupId>org.jline</groupId>
<artifactId>jline-reader</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline-style</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline-builtins</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline-console-ui</artifactId>
+ </dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal-jni</artifactId>
diff --git a/maven-settings-builder/pom.xml b/maven-settings-builder/pom.xml
index 36533dc3d0..5d442afdd7 100644
--- a/maven-settings-builder/pom.xml
+++ b/maven-settings-builder/pom.xml
@@ -62,10 +62,6 @@ under the License.
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-sec-dispatcher</artifactId>
</dependency>
- <dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-cipher</artifactId>
- </dependency>
<dependency>
<groupId>javax.inject</groupId>
diff --git
a/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/DefaultSettingsDecrypter.java
b/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/DefaultSettingsDecrypter.java
index 3695d6ab3b..59d1c13921 100644
---
a/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/DefaultSettingsDecrypter.java
+++
b/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/DefaultSettingsDecrypter.java
@@ -22,6 +22,7 @@ import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -58,28 +59,52 @@ public class DefaultSettingsDecrypter implements
SettingsDecrypter {
for (Server server : request.getServers()) {
server = server.clone();
- try {
- server.setPassword(decrypt(server.getPassword()));
- } catch (SecDispatcherException e) {
- problems.add(new DefaultSettingsProblem(
- "Failed to decrypt password for server " +
server.getId() + ": " + e.getMessage(),
- Severity.ERROR,
- "server: " + server.getId(),
- -1,
- -1,
- e));
+ String password = server.getPassword();
+ if (securityDispatcher.isAnyEncryptedString(password)) {
+ try {
+ if (securityDispatcher.isLegacyEncryptedString(password)) {
+ problems.add(new DefaultSettingsProblem(
+ "Legacy/insecurely encrypted password detected
for server " + server.getId(),
+ Severity.WARNING,
+ "server: " + server.getId(),
+ -1,
+ -1,
+ null));
+ }
+ server.setPassword(securityDispatcher.decrypt(password));
+ } catch (SecDispatcherException | IOException e) {
+ problems.add(new DefaultSettingsProblem(
+ "Failed to decrypt password for server " +
server.getId() + ": " + e.getMessage(),
+ Severity.ERROR,
+ "server: " + server.getId(),
+ -1,
+ -1,
+ e));
+ }
}
- try {
- server.setPassphrase(decrypt(server.getPassphrase()));
- } catch (SecDispatcherException e) {
- problems.add(new DefaultSettingsProblem(
- "Failed to decrypt passphrase for server " +
server.getId() + ": " + e.getMessage(),
- Severity.ERROR,
- "server: " + server.getId(),
- -1,
- -1,
- e));
+ String passphrase = server.getPassphrase();
+ if (securityDispatcher.isAnyEncryptedString(passphrase)) {
+ try {
+ if
(securityDispatcher.isLegacyEncryptedString(passphrase)) {
+ problems.add(new DefaultSettingsProblem(
+ "Legacy/insecurely encrypted passphrase
detected for server " + server.getId(),
+ Severity.WARNING,
+ "server: " + server.getId(),
+ -1,
+ -1,
+ null));
+ }
+
server.setPassphrase(securityDispatcher.decrypt(passphrase));
+ } catch (SecDispatcherException | IOException e) {
+ problems.add(new DefaultSettingsProblem(
+ "Failed to decrypt passphrase for server " +
server.getId() + ": " + e.getMessage(),
+ Severity.ERROR,
+ "server: " + server.getId(),
+ -1,
+ -1,
+ e));
+ }
}
servers.add(server);
@@ -88,16 +113,28 @@ public class DefaultSettingsDecrypter implements
SettingsDecrypter {
List<Proxy> proxies = new ArrayList<>();
for (Proxy proxy : request.getProxies()) {
- try {
- proxy.setPassword(decrypt(proxy.getPassword()));
- } catch (SecDispatcherException e) {
- problems.add(new DefaultSettingsProblem(
- "Failed to decrypt password for proxy " +
proxy.getId() + ": " + e.getMessage(),
- Severity.ERROR,
- "proxy: " + proxy.getId(),
- -1,
- -1,
- e));
+ String password = proxy.getPassword();
+ if (securityDispatcher.isAnyEncryptedString(password)) {
+ try {
+ if (securityDispatcher.isLegacyEncryptedString(password)) {
+ problems.add(new DefaultSettingsProblem(
+ "Legacy/insecurely encrypted password detected
for proxy " + proxy.getId(),
+ Severity.WARNING,
+ "proxy: " + proxy.getId(),
+ -1,
+ -1,
+ null));
+ }
+ proxy.setPassword(securityDispatcher.decrypt(password));
+ } catch (SecDispatcherException | IOException e) {
+ problems.add(new DefaultSettingsProblem(
+ "Failed to decrypt password for proxy " +
proxy.getId() + ": " + e.getMessage(),
+ Severity.ERROR,
+ "proxy: " + proxy.getId(),
+ -1,
+ -1,
+ e));
+ }
}
proxies.add(proxy);
@@ -105,8 +142,4 @@ public class DefaultSettingsDecrypter implements
SettingsDecrypter {
return new DefaultSettingsDecryptionResult(servers, proxies, problems);
}
-
- private String decrypt(String str) throws SecDispatcherException {
- return (str == null) ? null : securityDispatcher.decrypt(str);
- }
}
diff --git
a/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/MavenSecDispatcher.java
b/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/MavenSecDispatcher.java
new file mode 100644
index 0000000000..4ac37151d8
--- /dev/null
+++
b/maven-settings-builder/src/main/java/org/apache/maven/settings/crypto/MavenSecDispatcher.java
@@ -0,0 +1,58 @@
+/*
+ * 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.settings.crypto;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+
+import org.apache.maven.api.Constants;
+import org.codehaus.plexus.components.secdispatcher.Dispatcher;
+import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
+import
org.codehaus.plexus.components.secdispatcher.internal.DefaultSecDispatcher;
+
+/**
+ * This class implements "Maven specific" {@link SecDispatcher}.
+ *
+ * @deprecated since 4.0.0
+ */
+@Named
+@Singleton
+@Deprecated(since = "4.0.0")
+public class MavenSecDispatcher extends DefaultSecDispatcher {
+ private static final String FILE_NAME = "settings-security4.xml";
+
+ @Inject
+ public MavenSecDispatcher(Map<String, Dispatcher> dispatchers) {
+ super(dispatchers, configurationFile());
+ }
+
+ private static Path configurationFile() {
+ String mavenUserConf = System.getProperty(Constants.MAVEN_USER_CONF);
+ if (mavenUserConf != null) {
+ return Paths.get(mavenUserConf, FILE_NAME);
+ }
+ // this means we are in UT or alike
+ return Paths.get(System.getProperty("user.home"), ".m2", FILE_NAME);
+ }
+}
diff --git a/pom.xml b/pom.xml
index 1526eb14d3..e1fddf3ff2 100644
--- a/pom.xml
+++ b/pom.xml
@@ -167,7 +167,6 @@ under the License.
<assertjVersion>3.26.3</assertjVersion>
<asmVersion>9.7.1</asmVersion>
<byteBuddyVersion>1.15.3</byteBuddyVersion>
- <cipherVersion>3.0.0</cipherVersion>
<classWorldsVersion>2.8.0</classWorldsVersion>
<commonsCliVersion>1.9.0</commonsCliVersion>
<guiceVersion>6.0.0</guiceVersion>
@@ -186,7 +185,7 @@ under the License.
<plexusTestingVersion>1.4.0</plexusTestingVersion>
<plexusXmlVersion>4.0.4</plexusXmlVersion>
<resolverVersion>2.0.1</resolverVersion>
- <securityDispatcherVersion>3.0.0</securityDispatcherVersion>
+ <securityDispatcherVersion>4.0.1</securityDispatcherVersion>
<sisuVersion>0.9.0.M3</sisuVersion>
<slf4jVersion>2.0.16</slf4jVersion>
<stax2ApiVersion>4.2.2</stax2ApiVersion>
@@ -475,6 +474,21 @@ under the License.
<artifactId>jline-reader</artifactId>
<version>${jlineVersion}</version>
</dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline-style</artifactId>
+ <version>${jlineVersion}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline-builtins</artifactId>
+ <version>${jlineVersion}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jline</groupId>
+ <artifactId>jline-console-ui</artifactId>
+ <version>${jlineVersion}</version>
+ </dependency>
<dependency>
<groupId>org.jline</groupId>
<artifactId>jline-terminal-ffm</artifactId>
@@ -590,11 +604,6 @@ under the License.
<artifactId>plexus-sec-dispatcher</artifactId>
<version>${securityDispatcherVersion}</version>
</dependency>
- <dependency>
- <groupId>org.codehaus.plexus</groupId>
- <artifactId>plexus-cipher</artifactId>
- <version>${cipherVersion}</version>
- </dependency>
<dependency>
<groupId>com.fasterxml.woodstox</groupId>
<artifactId>woodstox-core</artifactId>