diff --git a/apache-maven/src/assembly/component.xml b/apache-maven/src/assembly/component.xml index 347dee1d5cd1..9a6d61dc5cc5 100644 --- a/apache-maven/src/assembly/component.xml +++ b/apache-maven/src/assembly/component.xml @@ -85,6 +85,7 @@ under the License. mvn mvnenc mvnDebug + mvnencDebug mvnyjp diff --git a/apache-maven/src/assembly/maven/bin/mvnencDebug b/apache-maven/src/assembly/maven/bin/mvnencDebug new file mode 100644 index 000000000000..50b3e6749250 --- /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 000000000000..22a869cd5bd9 --- /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 a70e856e3a3e..910d0375eaaf 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 @@ @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 cipher(); + Optional 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 masterSource(); - - /** - * Returns the dispatcher to use for dispatched encryption. - * - * @return an {@link Optional} containing the dispatcher string, or empty if not specified - */ - @Nonnull - Optional dispatcher(); + Optional yes(); /** * Returns the list of encryption goals to be executed. diff --git a/maven-cli/pom.xml b/maven-cli/pom.xml index 1ce18d63d50e..8c79d44942d2 100644 --- a/maven-cli/pom.xml +++ b/maven-cli/pom.xml @@ -92,6 +92,10 @@ under the License. + + org.eclipse.sisu + sisu-maven-plugin + org.apache.maven.plugins maven-jar-plugin 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 5ea9ec72dbae..ba65331388fe 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.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 @@ private ClingSupport(ClassWorld classWorld, boolean classWorldManaged) { * The main entry point. */ public int run(String[] args) throws IOException { - MessageUtils.systemInstall(); - MessageUtils.registerShutdownHook(); try (Invoker invoker = createInvoker()) { return invoker.invoke(parseArguments(args)); } catch (ParserException e) { @@ -75,12 +72,8 @@ public int run(String[] args) throws IOException { } 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 b8b204d5f470..691f207fb53d 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.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 MavenCling(ClassWorld classWorld) { 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> 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 488eb9eae44a..72cb51ea2c76 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.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 static int main(String[] args, ClassWorld world) throws IOException { return new MavenEncCling(world).run(args); } + private Terminal terminal; + public MavenEncCling() { super(); } @@ -60,10 +65,24 @@ public MavenEncCling(ClassWorld classWorld) { 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 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 c1fdb3e76a48..fcf964fee85c 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 @@ private static CommonsCliEncryptOptions interpolate( } @Override - public Optional cipher() { - if (commandLine.hasOption(CLIManager.CIPHER)) { - return Optional.of(commandLine.getOptionValue(CLIManager.CIPHER)); + public Optional force() { + if (commandLine.hasOption(CLIManager.FORCE)) { + return Optional.of(Boolean.TRUE); } return Optional.empty(); } @Override - public Optional masterSource() { - if (commandLine.hasOption(CLIManager.MASTER_SOURCE)) { - return Optional.of(commandLine.getOptionValue(CLIManager.MASTER_SOURCE)); - } - return Optional.empty(); - } - - @Override - public Optional dispatcher() { - if (commandLine.hasOption(CLIManager.DISPATCHER)) { - return Optional.of(commandLine.getOptionValue(CLIManager.DISPATCHER)); + public Optional yes() { + if (commandLine.hasOption(CLIManager.YES)) { + return Optional.of(Boolean.TRUE); } return Optional.empty(); } @@ -109,24 +101,19 @@ public EncryptOptions interpolate(Collection> properties) { } 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 000000000000..a3981a789720 --- /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 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 ff0cb4d05bd6..82566bbe3f75 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 implements EncryptInvoker { + @SuppressWarnings("VisibilityModifier") public static class LocalContext extends LookupInvokerContext { protected LocalContext(DefaultEncryptInvoker invoker, EncryptInvokerRequest invokerRequest) { super(invoker, invokerRequest); } - protected SecDispatcher secDispatcher; + public Map goals; + + public List 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 @@ protected LocalContext createContext(EncryptInvokerRequest invokerRequest) { @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 000000000000..d493b289e36c --- /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 000000000000..5719a816fc3a --- /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> entry : + response.getReport().entrySet()) { + Consumer 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 000000000000..6c437c96767b --- /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 000000000000..01d4680ac92b --- /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 000000000000..c17a5bd53ffd --- /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 000000000000..8a8b43ebb92a --- /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 000000000000..cd21e4abc2bc --- /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 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> editables = result.entrySet().stream() + .filter(e -> e.getValue().getResult().contains("$")) + .toList(); + if (!editables.isEmpty()) { + context.addInHeader(""); + context.addInHeader("Please customize the editable value:"); + Map editMap; + for (Map.Entry 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 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 f49ef3b727b2..9c214a5da41a 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 SessionBuilder newRepositorySessionBuilder(MavenExecutionRequest request) 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 a5fb2351e0a8..5a36cf9fea49 100644 --- a/maven-embedder/pom.xml +++ b/maven-embedder/pom.xml @@ -88,10 +88,6 @@ under the License. org.codehaus.plexus plexus-sec-dispatcher - - org.codehaus.plexus - plexus-cipher - org.codehaus.plexus plexus-interpolation diff --git a/maven-jline/pom.xml b/maven-jline/pom.xml index 867bc34b41a2..09eb58de33af 100644 --- a/maven-jline/pom.xml +++ b/maven-jline/pom.xml @@ -42,6 +42,18 @@ under the License. org.jline jline-reader + + org.jline + jline-style + + + org.jline + jline-builtins + + + org.jline + jline-console-ui + org.jline jline-terminal-jni diff --git a/maven-settings-builder/pom.xml b/maven-settings-builder/pom.xml index 36533dc3d087..5d442afdd7f7 100644 --- a/maven-settings-builder/pom.xml +++ b/maven-settings-builder/pom.xml @@ -62,10 +62,6 @@ under the License. org.codehaus.plexus plexus-sec-dispatcher - - org.codehaus.plexus - plexus-cipher - javax.inject 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 3695d6ab3b45..59d1c1392120 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.Named; import javax.inject.Singleton; +import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -58,28 +59,52 @@ public SettingsDecryptionResult decrypt(SettingsDecryptionRequest request) { 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 SettingsDecryptionResult decrypt(SettingsDecryptionRequest request) { List 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 SettingsDecryptionResult decrypt(SettingsDecryptionRequest request) { 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 000000000000..4ac37151d812 --- /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 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 1526eb14d32b..e1fddf3ff2ed 100644 --- a/pom.xml +++ b/pom.xml @@ -167,7 +167,6 @@ under the License. 3.26.3 9.7.1 1.15.3 - 3.0.0 2.8.0 1.9.0 6.0.0 @@ -186,7 +185,7 @@ under the License. 1.4.0 4.0.4 2.0.1 - 3.0.0 + 4.0.1 0.9.0.M3 2.0.16 4.2.2 @@ -475,6 +474,21 @@ under the License. jline-reader ${jlineVersion} + + org.jline + jline-style + ${jlineVersion} + + + org.jline + jline-builtins + ${jlineVersion} + + + org.jline + jline-console-ui + ${jlineVersion} + org.jline jline-terminal-ffm @@ -590,11 +604,6 @@ under the License. plexus-sec-dispatcher ${securityDispatcherVersion} - - org.codehaus.plexus - plexus-cipher - ${cipherVersion} - com.fasterxml.woodstox woodstox-core