mirror of https://github.com/apache/maven.git
[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
This commit is contained in:
parent
7df5b16f78
commit
f5e54ca6fa
|
@ -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>
|
||||
|
|
|
@ -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" "$@"
|
|
@ -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 %*
|
|
@ -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.
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
options.addOption(Option.builder(FORCE)
|
||||
.longOpt("force")
|
||||
.desc("Should overwrite without asking any configuration?")
|
||||
.build());
|
||||
options.addOption(Option.builder(MASTER_SOURCE)
|
||||
.longOpt("master-source")
|
||||
.desc("The master source that user wants to use for non-dispatched encryption")
|
||||
.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());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
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;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
23
pom.xml
23
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>
|
||||
|
|
Loading…
Reference in New Issue