org.codehaus.plexus.components.secdispatcher.PinEntry Maven / Gradle / Ivy
The newest version!
/*
* 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.codehaus.plexus.components.secdispatcher;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.util.Objects.requireNonNull;
/**
* Inspired by A peek inside pinentry.
* Also look at Pinentry Documentation.
* Finally, source mirror is at gpg/pinentry.
*/
public class PinEntry {
public enum Outcome {
SUCCESS,
TIMEOUT,
NOT_CONFIRMED,
CANCELED,
FAILED;
}
public record Result(Outcome outcome, String payload) {}
private final Logger logger = LoggerFactory.getLogger(getClass());
private final String cmd;
private final LinkedHashMap commands;
/**
* Creates pin entry instance that will use the passed in cmd executable.
*/
public PinEntry(String cmd) {
this.cmd = requireNonNull(cmd);
this.commands = new LinkedHashMap<>();
}
/**
* Sets a "stable key handle" for caching purposes. Optional.
*/
public PinEntry setKeyInfo(String keyInfo) {
requireNonNull(keyInfo);
commands.put("OPTION", "allow-external-password-cache");
commands.put("SETKEYINFO", keyInfo);
return this;
}
/**
* Sets the OK button label, by default "Ok".
*/
public PinEntry setOk(String msg) {
requireNonNull(msg);
commands.put("SETOK", msg);
return this;
}
/**
* Sets the CANCEL button label, by default "Cancel".
*/
public PinEntry setCancel(String msg) {
requireNonNull(msg);
commands.put("SETCANCEL", msg);
return this;
}
/**
* Sets the window title.
*/
public PinEntry setTitle(String title) {
requireNonNull(title);
commands.put("SETTITLE", title);
return this;
}
/**
* Sets additional test in window.
*/
public PinEntry setDescription(String desc) {
requireNonNull(desc);
commands.put("SETDESC", desc);
return this;
}
/**
* Sets the prompt.
*/
public PinEntry setPrompt(String prompt) {
requireNonNull(prompt);
commands.put("SETPROMPT", prompt);
return this;
}
/**
* If set, window will show "Error: xxx", usable for second attempt (ie "bad password").
*/
public PinEntry setError(String error) {
requireNonNull(error);
commands.put("SETERROR", error);
return this;
}
/**
* Usable with {@link #getPin()}, window will contain two input fields and will force user to type in same
* input in both fields, ie to "confirm" the pin.
*/
public PinEntry confirmPin() {
commands.put("SETREPEAT", null);
return this;
}
/**
* Sets the window timeout, if no button pressed and timeout passes, Result will by {@link Outcome#TIMEOUT}.
*/
public PinEntry setTimeout(Duration timeout) {
long seconds = timeout.toSeconds();
if (seconds < 0) {
throw new IllegalArgumentException("Set timeout is 0 seconds");
}
commands.put("SETTIMEOUT", String.valueOf(seconds));
return this;
}
/**
* Initiates a "get pin" dialogue with input field(s) using previously set options.
*/
public Result getPin() throws IOException {
commands.put("GETPIN", null);
return execute();
}
/**
* Initiates a "confirmation" dialogue (no input) using previously set options.
*/
public Result confirm() throws IOException {
commands.put("CONFIRM", null);
return execute();
}
/**
* Initiates a "message" dialogue (no input) using previously set options.
*/
public Result message() throws IOException {
commands.put("MESSAGE", null);
return execute();
}
private Result execute() throws IOException {
Process process = new ProcessBuilder(cmd).start();
BufferedReader reader = process.inputReader();
BufferedWriter writer = process.outputWriter();
expectOK(process.inputReader());
Map.Entry lastEntry = commands.entrySet().iterator().next();
for (Map.Entry entry : commands.entrySet()) {
String cmd;
if (entry.getValue() != null) {
cmd = entry.getKey() + " " + entry.getValue();
} else {
cmd = entry.getKey();
}
logger.debug("> {}", cmd);
writer.write(cmd);
writer.newLine();
writer.flush();
if (entry != lastEntry) {
expectOK(reader);
}
}
Result result = lastExpect(reader);
writer.write("BYE");
writer.newLine();
writer.flush();
try {
process.waitFor(5, TimeUnit.SECONDS);
int exitCode = process.exitValue();
if (exitCode != 0) {
return new Result(Outcome.FAILED, "Exit code: " + exitCode);
} else {
return result;
}
} catch (Exception e) {
return new Result(Outcome.FAILED, e.getMessage());
}
}
private void expectOK(BufferedReader in) throws IOException {
String response = in.readLine();
logger.debug("< {}", response);
if (!response.startsWith("OK")) {
throw new IOException("Expected OK but got this instead: " + response);
}
}
private Result lastExpect(BufferedReader in) throws IOException {
while (true) {
String response = in.readLine();
logger.debug("< {}", response);
if (response.startsWith("#")) {
continue;
}
if (response.startsWith("S")) {
continue;
}
if (response.startsWith("ERR")) {
if (response.contains("83886142")) {
return new Result(Outcome.TIMEOUT, response);
}
if (response.contains("83886179")) {
return new Result(Outcome.CANCELED, response);
}
if (response.contains("83886194")) {
return new Result(Outcome.NOT_CONFIRMED, response);
}
}
if (response.startsWith("D")) {
return new Result(Outcome.SUCCESS, response.substring(2));
}
if (response.startsWith("OK")) {
return new Result(Outcome.SUCCESS, response);
}
}
}
public static void main(String[] args) throws IOException {
// check what pinentry apps you have and replace the execName
String cmd = "/usr/bin/pinentry-gnome3";
Result pinResult = new PinEntry(cmd)
.setTimeout(Duration.ofSeconds(15))
.setKeyInfo("maven:masterPassword")
.setTitle("Maven Master Password")
.setDescription("Please enter the Maven master password")
.setPrompt("Password")
.setOk("Here you go!")
.setCancel("Uh oh, rather not")
// .confirmPin() (will not let you through if you cannot type same thing twice)
.getPin();
if (pinResult.outcome() == Outcome.SUCCESS) {
Result confirmResult = new PinEntry(cmd)
.setTitle("Password confirmation")
.setPrompt("Please confirm the password")
.setDescription("Is the password '" + pinResult.payload() + "' the one you want?")
.confirm();
if (confirmResult.outcome() == Outcome.SUCCESS) {
new PinEntry(cmd)
.setTitle("Password confirmed")
.setPrompt("The password '" + pinResult.payload() + "' is confirmed.")
.setDescription("You confirmed your password")
.message();
} else {
System.out.println(confirmResult);
}
} else {
System.out.println(pinResult);
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy