GP-5362 Correct password entry bug and refactor PasswordDialog/ClientAuthenticator

This commit is contained in:
ghidra1 2025-02-13 14:11:30 -05:00
parent 99ed1003b5
commit d0badde92b
12 changed files with 194 additions and 116 deletions

View file

@ -418,10 +418,6 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider {
} }
String user = userField.getText().trim(); String user = userField.getText().trim();
if (ClientUtil.getUserName().equals(user)) {
user = null;
}
String name = nameField.getText().trim(); String name = nameField.getText().trim();
String host = hostField.getText().trim(); String host = hostField.getText().trim();

View file

@ -334,14 +334,16 @@ public class BSimPostgresDBConnectionManager {
throw new SQLException("No registered authenticator"); throw new SQLException("No registered authenticator");
} }
NameCallback nameCb = new NameCallback("User ID:", bds.getUsername()); NameCallback nameCb = new NameCallback("User ID:", bds.getUsername());
boolean allowUserIDEntry = true;
if (!serverInfo.hasDefaultLogin()) { if (!serverInfo.hasDefaultLogin()) {
nameCb.setName(bds.getUsername()); nameCb.setName(bds.getUsername());
allowUserIDEntry = false;
} }
PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting
try { try {
if (!clientAuthenticator.processPasswordCallbacks( if (!clientAuthenticator.processPasswordCallbacks(
"BSim Database Authentication", "BSim Database Server", "BSim Database Authentication", "BSim DB Server", serverInfo.toString(),
serverInfo.toString(), nameCb, passCb, null, null, loginError)) { allowUserIDEntry, nameCb, passCb, null, null, loginError)) {
throw new CancelledException(); throw new CancelledException();
} }
bds.setPassword(new String(passCb.getPassword())); bds.setPassword(new String(passCb.getPassword()));

View file

@ -376,7 +376,7 @@ public class BSimServerInfo implements Comparable<BSimServerInfo> {
} }
/** /**
* Determine of user info was stipulated during construction * Determine if user info was stipulated during construction
* @return true if user info was stipulated during construction * @return true if user info was stipulated during construction
*/ */
public boolean hasDefaultLogin() { public boolean hasDefaultLogin() {

View file

@ -590,8 +590,8 @@ public abstract class GhidraScript extends FlatProgramAPI {
if (isRunningHeadless()) { if (isRunningHeadless()) {
// only change client authenticator in headless mode // only change client authenticator in headless mode
try { try {
HeadlessClientAuthenticator.installHeadlessClientAuthenticator( HeadlessClientAuthenticator
ClientUtil.getUserName(), null, false); .installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false);
} }
catch (IOException e) { catch (IOException e) {
throw new RuntimeException("Unexpected Exception", e); throw new RuntimeException("Unexpected Exception", e);
@ -2629,8 +2629,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
Address choice = doAsk(Integer.class, title, message, existingValue, lastValue -> { Address choice = doAsk(Integer.class, title, message, existingValue, lastValue -> {
AskAddrDialog dialog = AskAddrDialog dialog = new AskAddrDialog(title, message, currentProgram, lastValue);
new AskAddrDialog(title, message, currentProgram, lastValue);
if (dialog.isCanceled()) { if (dialog.isCanceled()) {
throw new CancelledException(); throw new CancelledException();
} }
@ -3158,7 +3157,7 @@ public abstract class GhidraScript extends FlatProgramAPI {
throw new ImproperUseException( throw new ImproperUseException(
"The askPassword() method can only be used when running headed Ghidra."); "The askPassword() method can only be used when running headed Ghidra.");
} }
PasswordDialog dialog = new PasswordDialog(title, null, null, prompt, null, null); PasswordDialog dialog = new PasswordDialog(title, null, null, prompt);
try { try {
state.getTool().showDialog(dialog); state.getTool().showDialog(dialog);
if (!dialog.okWasPressed()) { if (!dialog.okWasPressed()) {

View file

@ -20,6 +20,8 @@ import java.awt.event.*;
import javax.swing.*; import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.DialogComponentProvider; import docking.DialogComponentProvider;
import docking.widgets.checkbox.GCheckBox; import docking.widgets.checkbox.GCheckBox;
import docking.widgets.combobox.GComboBox; import docking.widgets.combobox.GComboBox;
@ -39,26 +41,34 @@ public class PasswordDialog extends DialogComponentProvider {
private JComboBox<String> choiceCB; private JComboBox<String> choiceCB;
private JCheckBox anonymousAccess; private JCheckBox anonymousAccess;
private boolean okPressed = false; private boolean okPressed = false;
private String defaultUserID; private String defaultUserId;
/** /**
* Construct a new PasswordDialog. * Construct a new PasswordDialog which may include user ID specification/prompt, if either
* {@code allowUserIdEntry} is true or a non-null {@code defaultUserId} has been specified, and
* other optional elements. The dialog includes a message text area which supports the use
* of {@link #setErrorText(String)}.
*
* @param title title of the dialog * @param title title of the dialog
* @param serverType 'Server' or 'Key-store' designation * @param serverType 'Server' or 'Key-store' designation
* @param serverName name of server or keystore pathname * @param serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null, in which case * @param passPrompt password prompt to show in the dialog; may be null/empty, in which case
* "Password:" is displayed next to the password field * "Password:" is displayed next to the password field
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null a name will not be prompted for. * @param allowUserIdEntry if true user ID entry will be supported
* @param defaultUserID default name when prompting for a name * @param userIdPrompt User ID / Name prompt to show in the dialog, if null "User ID:" is prompt
* if either {@code allowUserIdEntry} is true or a non-null {@code defaultUserId} has been specified.
* @param defaultUserId default name when prompting for a name
* @param choicePrompt namePrompt name prompt to show in the dialog, if null a name will not be prompted for. * @param choicePrompt namePrompt name prompt to show in the dialog, if null a name will not be prompted for.
* @param choices array of choices to present if choicePrompt is not null * @param choices array of choices to present if choicePrompt is not null
* @param defaultChoice default choice index * @param defaultChoice default choice index
* @param includeAnonymousOption true signals to add a checkbox to request anonymous login * @param includeAnonymousOption true signals to add a checkbox to request anonymous login
*/ */
public PasswordDialog(String title, String serverType, String serverName, String passPrompt, public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
String userIdPrompt, String defaultUserID, String choicePrompt, String[] choices, boolean allowUserIdEntry, String userIdPrompt, String defaultUserId,
int defaultChoice, boolean includeAnonymousOption) { String choicePrompt, String[] choices, int defaultChoice,
this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID); boolean includeAnonymousOption) {
this(title, serverType, serverName, passPrompt, allowUserIdEntry, userIdPrompt,
defaultUserId, true);
if (choicePrompt != null) { if (choicePrompt != null) {
workPanel.add(new GLabel(choicePrompt)); workPanel.add(new GLabel(choicePrompt));
choiceCB = new GComboBox<>(choices); choiceCB = new GComboBox<>(choices);
@ -89,43 +99,70 @@ public class PasswordDialog extends DialogComponentProvider {
} }
/** /**
* Construct a new PasswordDialog. * Construct a new PasswordDialog which only prompts for a password for a specified server
* type and name. The dialog will not include a User ID display, although server fields
* may be used for a similar display purpose. The dialog includes a message text area
* which supports the use of {@link #setErrorText(String)}.
*
* @param title title of the dialog * @param title title of the dialog
* @param serverType 'Server' or 'Key-store' designation * @param serverType 'Server' or 'Key-store' designation
* @param serverName name of server or keystore pathname * @param serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null, in which case * @param passPrompt password prompt to show in the dialog; may be null, in which case
* "Password:" is displayed next to the password field * "Password:" is prompt.
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null a name will not be prompted for.
* @param defaultUserID default name when prompting for a name
*/ */
public PasswordDialog(String title, String serverType, String serverName, String passPrompt, public PasswordDialog(String title, String serverType, String serverName, String passPrompt) {
String userIdPrompt, String defaultUserID) { this(title, serverType, serverName, passPrompt, true);
this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID, true);
} }
/** /**
* Construct a new PasswordDialog. * Construct a new PasswordDialog which only prompts for a password for a specified server
* type and name. The dialog will not include a User ID display, although server fields
* may be used for a similar display purpose. The dialog optionally includes a message
* text area which supports the use of {@link #setErrorText(String)}.
*
* @param title title of the dialog * @param title title of the dialog
* @param serverType 'Server' or 'Key-store' designation * @param serverType 'Server' or 'Key-store' designation
* @param serverName name of server or keystore pathname * @param serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null, in which case * @param passPrompt password prompt to show in the dialog; may be null, in which case
* "Password:" is displayed next to the password field * "Password:" is displayed next to the password field
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null a name will not be prompted for. * @param hasMessages true if a message text area should be included allowing for use of
* @param defaultUserID default name when prompting for a name * {@link #setErrorText(String)}
* @param hasMessages true if the client will set messages on this dialog. If true, the
* dialog's minimum size will be increased
*/ */
public PasswordDialog(String title, String serverType, String serverName, String passPrompt, public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
String userIdPrompt, String defaultUserID, boolean hasMessages) { boolean hasMessages) {
this(title, serverType, serverName, passPrompt, false, null, null, hasMessages);
}
/**
* Construct a new PasswordDialog which may include user ID specification/prompt if either
* {@code allowUserIdEntry} is true or a non-null {@code defaultUserId} has been specified.
* The dialog optionally includes a message text area area which supports the use of
* {@link #setErrorText(String)}.
*
* @param title title of the dialog
* @param serverType 'Server' or 'Key-store' designation
* @param serverName name of server or keystore pathname
* @param passPrompt password prompt to show in the dialog; may be null/empty, in which case
* "Password:" is displayed next to the password field
* @param allowUserIdEntry if true user ID entry will be supported
* @param userIdPrompt User ID / Name prompt to show in the dialog, if null "User ID:" is prompt
* if either {@code allowUserIdEntry} is true or a non-null {@code defaultUserId} has been specified.
* @param defaultUserId default name when prompting for a name
* @param hasMessages true if a message text area should be included allowing for use of
* {@link #setErrorText(String)}
*/
public PasswordDialog(String title, String serverType, String serverName, String passPrompt,
boolean allowUserIdEntry, String userIdPrompt, String defaultUserId,
boolean hasMessages) {
super(title, true); super(title, true);
this.defaultUserID = defaultUserID; this.defaultUserId = defaultUserId;
setRememberSize(false); setRememberSize(false);
setTransient(true); setTransient(true);
if (hasMessages) { if (hasMessages) {
setMinimumSize(300, 150); setMinimumSize(350, 150);
} }
workPanel = new JPanel(new PairLayout(5, 5)); workPanel = new JPanel(new PairLayout(5, 5));
@ -136,20 +173,27 @@ public class PasswordDialog extends DialogComponentProvider {
workPanel.add(new GLabel(serverName)); workPanel.add(new GLabel(serverName));
} }
if (userIdPrompt != null) { if (StringUtils.isBlank(userIdPrompt)) {
userIdPrompt = "User ID:";
}
if (StringUtils.isBlank(passPrompt)) {
passPrompt = "Password:";
}
if (allowUserIdEntry) {
workPanel.add(new GLabel(userIdPrompt)); workPanel.add(new GLabel(userIdPrompt));
nameField = new JTextField(defaultUserID, 16); nameField = new JTextField(defaultUserId, 16);
nameField.setName("NAME-ENTRY-COMPONENT"); nameField.setName("NAME-ENTRY-COMPONENT");
workPanel.add(nameField); workPanel.add(nameField);
} }
else if (defaultUserID != null) { else if (defaultUserId != null) {
workPanel.add(new GLabel("User ID:")); workPanel.add(new GLabel(userIdPrompt));
JLabel nameLabel = new GLabel(defaultUserID); JLabel nameLabel = new GLabel(defaultUserId);
nameLabel.setName("NAME-COMPONENT"); nameLabel.setName("NAME-COMPONENT");
workPanel.add(nameLabel); workPanel.add(nameLabel);
} }
workPanel.add(new GLabel(passPrompt != null ? passPrompt : "Password:")); workPanel.add(new GLabel(passPrompt));
passwordField = new JPasswordField(16); passwordField = new JPasswordField(16);
passwordField.setName("PASSWORD-ENTRY-COMPONENT"); passwordField.setName("PASSWORD-ENTRY-COMPONENT");
workPanel.add(passwordField); workPanel.add(passwordField);
@ -245,7 +289,7 @@ public class PasswordDialog extends DialogComponentProvider {
* @return the user ID / Name entered in the password field * @return the user ID / Name entered in the password field
*/ */
public String getUserID() { public String getUserID() {
return nameField != null ? nameField.getText().trim() : defaultUserID; return nameField != null ? nameField.getText().trim() : defaultUserId;
} }
/** /**

View file

@ -1,13 +1,12 @@
/* ### /* ###
* IP: GHIDRA * IP: GHIDRA
* REVIEWED: YES
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -63,7 +62,7 @@ public class PopupKeyStorePasswordProvider implements KeyStorePasswordProvider {
@Override @Override
public void run() { public void run() {
PasswordDialog pwdDialog = PasswordDialog pwdDialog =
new PasswordDialog("Protected PKI Certificate", "Cert File", file, null, null, null); new PasswordDialog("Protected PKI Certificate", "Cert File", file, null);
if (passwordError) { if (passwordError) {
pwdDialog.setErrorText("Incorrect password"); pwdDialog.setErrorText("Incorrect password");
} }

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -139,8 +139,8 @@ public class SplashScreenTest extends AbstractDockingTest {
private DockingDialog showModalPasswordDialog(Frame parentFrame) throws Exception { private DockingDialog showModalPasswordDialog(Frame parentFrame) throws Exception {
String dialogTitle = "InfoWindowTest.testSplashScreenPasswordModality() Dialog"; String dialogTitle = "InfoWindowTest.testSplashScreenPasswordModality() Dialog";
DialogComponentProvider passwordDialog = runSwing(() -> new PasswordDialog(dialogTitle, DialogComponentProvider passwordDialog =
"Server Type", "Server Name", "Prompt", null, null)); runSwing(() -> new PasswordDialog(dialogTitle, "Server Type", "Server Name", "Prompt"));
if (parentFrame == null) { if (parentFrame == null) {
// null means to share the parent // null means to share the parent

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -33,10 +33,11 @@ public interface ClientAuthenticator extends KeyStorePasswordProvider {
public Authenticator getAuthenticator(); public Authenticator getAuthenticator();
/** /**
* Process Ghidra Server password authentication callbacks. * Process password authentication callbacks.
* @param title password prompt title if GUI is used * @param title password prompt title if GUI is used
* @param serverType type of server (label associated with serverName) * @param serverType type of server (label associated with serverName)
* @param serverName name of server * @param serverName name of server
* @param allowUserNameEntry if true user ID entry will be supported if nameCb is not null.
* @param nameCb provides storage for user login name. A null indicates * @param nameCb provides storage for user login name. A null indicates
* that the default user name will be used, @see ClientUtil#getUserName() * that the default user name will be used, @see ClientUtil#getUserName()
* @param passCb provides storage for user password, @see PasswordCallback#setPassword(char[]) * @param passCb provides storage for user password, @see PasswordCallback#setPassword(char[])
@ -51,8 +52,8 @@ public interface ClientAuthenticator extends KeyStorePasswordProvider {
* @return true if password provided, false if entry cancelled * @return true if password provided, false if entry cancelled
*/ */
public boolean processPasswordCallbacks(String title, String serverType, String serverName, public boolean processPasswordCallbacks(String title, String serverType, String serverName,
NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb,
AnonymousCallback anonymousCb, String loginError); ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError);
/** /**
* Prompt user for reconnect * Prompt user for reconnect

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -425,7 +425,8 @@ public class ClientUtil {
"Unsupported authentication callback: " + callbacks[0].getClass().getName()); "Unsupported authentication callback: " + callbacks[0].getClass().getName());
} }
if (!clientAuthenticator.processPasswordCallbacks("Repository Server Authentication", if (!clientAuthenticator.processPasswordCallbacks("Repository Server Authentication",
"Repository Server", serverName, nameCb, passCb, choiceCb, anonymousCb, loginError)) { "Repository Server", serverName, nameCb != null, nameCb, passCb, choiceCb, anonymousCb,
loginError)) {
return false; return false;
} }
String name = defaultUserID; String name = defaultUserID;

View file

@ -72,13 +72,14 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
if (pwd != null) { if (pwd != null) {
// Requesting URL specified password // Requesting URL specified password
return new PasswordAuthentication(userName, pwd.toCharArray()); return new PasswordAuthentication(userName, pwd.toCharArray());
} }
NameCallback nameCb = new NameCallback("Name: ", userName); NameCallback nameCb = new NameCallback("Name: ", userName);
boolean allowUserIDEntry = true;
if (!useDefaultUser) { if (!useDefaultUser) {
// Prevent modification of user name by password prompting
nameCb.setName(userName); nameCb.setName(userName);
allowUserIDEntry = false;
} }
// Prompt for password // Prompt for password
@ -89,10 +90,10 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
PasswordCallback passCb = new PasswordCallback(prompt, false); PasswordCallback passCb = new PasswordCallback(prompt, false);
try { try {
ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication", ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication",
"Server", serverName, nameCb, passCb, null, null, null); "Server", serverName, allowUserIDEntry, nameCb, passCb, null, null, null);
SystemUtilities.runSwingNow(pp); SystemUtilities.runSwingNow(pp);
if (pp.okWasPressed()) { if (pp.okWasPressed()) {
return new PasswordAuthentication(nameCb.getName(), passCb.getPassword()); return new PasswordAuthentication(nameCb.getName(), passCb.getPassword());
} }
} }
finally { finally {
@ -135,10 +136,10 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
@Override @Override
public boolean processPasswordCallbacks(String title, String serverType, String serverName, public boolean processPasswordCallbacks(String title, String serverType, String serverName,
NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb,
AnonymousCallback anonymousCb, String loginError) { ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) {
ServerPasswordPrompt pp = new ServerPasswordPrompt(title, serverType, serverName, nameCb, ServerPasswordPrompt pp = new ServerPasswordPrompt(title, serverType, serverName,
passCb, choiceCb, anonymousCb, loginError); allowUserNameEntry, nameCb, passCb, choiceCb, anonymousCb, loginError);
SystemUtilities.runSwingNow(pp); SystemUtilities.runSwingNow(pp);
return pp.okWasPressed(); return pp.okWasPressed();
} }
@ -172,6 +173,7 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
private String title; private String title;
private String serverType; // label for serverName field private String serverType; // label for serverName field
private String serverName; private String serverName;
private boolean allowUserIDEntry;
private NameCallback nameCb; private NameCallback nameCb;
private PasswordCallback passCb; private PasswordCallback passCb;
private ChoiceCallback choiceCb; private ChoiceCallback choiceCb;
@ -180,11 +182,12 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
private boolean okPressed = false; private boolean okPressed = false;
ServerPasswordPrompt(String title, String serverType, String serverName, ServerPasswordPrompt(String title, String serverType, String serverName,
NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, boolean allowUserIDEntry, NameCallback nameCb, PasswordCallback passCb,
AnonymousCallback anonymousCb, String errorMsg) { ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String errorMsg) {
this.title = title; this.title = title;
this.serverType = serverType; this.serverType = serverType;
this.serverName = serverName; this.serverName = serverName;
this.allowUserIDEntry = allowUserIDEntry && (nameCb != null);
this.nameCb = nameCb; this.nameCb = nameCb;
this.passCb = passCb; this.passCb = passCb;
this.choiceCb = choiceCb; this.choiceCb = choiceCb;
@ -225,20 +228,20 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
String defaultUserName = null; String defaultUserName = null;
String namePrompt = null; String namePrompt = null;
if (nameCb != null) { if (nameCb != null) {
namePrompt = nameCb.getPrompt();
defaultUserName = nameCb.getName(); defaultUserName = nameCb.getName();
if (defaultUserName == null) { if (StringUtils.isBlank(defaultUserName)) {
// Name entry only permitted with name callback where name has not be pre-set
defaultUserName = nameCb.getDefaultName(); defaultUserName = nameCb.getDefaultName();
namePrompt = nameCb.getPrompt();
} }
} }
if (defaultUserName == null) {
if (StringUtils.isBlank(defaultUserName)) {
defaultUserName = getDefaultUserName(); defaultUserName = getDefaultUserName();
} }
PasswordDialog pwdDialog = new PasswordDialog(title, serverType, serverName, PasswordDialog pwdDialog = new PasswordDialog(title, serverType, serverName,
passCb.getPrompt(), namePrompt, defaultUserName, choicePrompt, choices, passCb.getPrompt(), allowUserIDEntry, namePrompt, defaultUserName, choicePrompt,
getDefaultChoice(), anonymousCb != null); choices, getDefaultChoice(), anonymousCb != null);
if (errorMsg != null) { if (errorMsg != null) {
pwdDialog.setErrorText(errorMsg); pwdDialog.setErrorText(errorMsg);
@ -252,7 +255,7 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider
} }
else { else {
passCb.setPassword(pwdDialog.getPassword()); passCb.setPassword(pwdDialog.getPassword());
if (nameCb != null) { if (nameCb != null && allowUserIDEntry) {
String username = pwdDialog.getUserID(); String username = pwdDialog.getUserID();
nameCb.setName(username); nameCb.setName(username);
Preferences.setProperty(NAME_PREFERENCE, username); Preferences.setProperty(NAME_PREFERENCE, username);

View file

@ -39,22 +39,25 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
private final static char[] BADPASSWORD = "".toCharArray(); private final static char[] BADPASSWORD = "".toCharArray();
private static Object sshPrivateKey; private static Object sshPrivateKey;
private static String defaultUserName = ClientUtil.getUserName(); private static String preferredName = null;
private static boolean passwordPromptAllowed; private static boolean passwordPromptAllowed;
/**
* Simple authentication handler using a default authenticator which may be passed to
* {@link Authenticator#setDefault(Authenticator)}. The preferred username must be set by
* invoking {@link #installHeadlessClientAuthenticator(String, String, boolean)} or
* stipulated by the requesting URL. The {@link ClientUtil#getUserName()} identity is never
* used.
*/
private Authenticator authenticator = new Authenticator() { private Authenticator authenticator = new Authenticator() {
@Override @Override
protected PasswordAuthentication getPasswordAuthentication() { protected PasswordAuthentication getPasswordAuthentication() {
if (defaultUserName == null) {
throw new IllegalStateException("Default user name is unknown");
}
String serverName = getRequestingHost(); String serverName = getRequestingHost();
URL requestingURL = getRequestingURL(); // may be null URL requestingURL = getRequestingURL(); // may be null
String pwd = null; String pwd = null;
String userName = defaultUserName; String name = preferredName;
if (requestingURL != null) { if (requestingURL != null) {
String userInfo = requestingURL.getUserInfo(); String userInfo = requestingURL.getUserInfo();
@ -62,12 +65,12 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
// Use user info from URL // Use user info from URL
int pwdSep = userInfo.indexOf(':'); int pwdSep = userInfo.indexOf(':');
if (pwdSep < 0) { if (pwdSep < 0) {
userName = userInfo; name = userInfo;
} }
else { else {
pwd = userInfo.substring(pwdSep + 1); pwd = userInfo.substring(pwdSep + 1);
if (pwdSep != 0) { if (pwdSep != 0) {
userName = userInfo.substring(0, pwdSep); name = userInfo.substring(0, pwdSep);
} }
} }
} }
@ -77,20 +80,24 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
serverName = minimalURL.toExternalForm(); serverName = minimalURL.toExternalForm();
} }
} }
Msg.debug(this, "PasswordAuthentication requested for " + serverName); Msg.debug(this, "PasswordAuthentication requested for " + serverName);
if (StringUtils.isBlank(name)) {
throw new IllegalStateException("Connection user name is unknown");
}
if (pwd != null) { if (pwd != null) {
// Requesting URL specified password // Requesting URL specified password
return new PasswordAuthentication(userName, pwd.toCharArray()); return new PasswordAuthentication(name, pwd.toCharArray());
} }
String usage = "Access password requested for " + serverName; String usage = "Access password requested for " + serverName;
String prompt = getRequestingPrompt(); String prompt = getRequestingPrompt();
if (StringUtils.isBlank(prompt) || "security".equals(prompt)) { if (StringUtils.isBlank(prompt) || "security".equals(prompt)) {
prompt = "Password for " + userName +":"; prompt = "Password for " + name + ":";
} }
return new PasswordAuthentication(userName, getPassword(usage, prompt)); return new PasswordAuthentication(name, getPassword(usage, prompt));
} }
}; };
@ -103,7 +110,9 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
} }
/** /**
* Install headless client authenticator for Ghidra Server * Install headless client authenticator for Ghidra Server and when http/https
* connections require authentication and have not specified user information.
*
* @param username optional username to be used with a Ghidra Server which * @param username optional username to be used with a Ghidra Server which
* allows username to be specified. If null, {@link ClientUtil#getUserName()} * allows username to be specified. If null, {@link ClientUtil#getUserName()}
* will be used. * will be used.
@ -118,7 +127,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
boolean allowPasswordPrompt) throws IOException { boolean allowPasswordPrompt) throws IOException {
passwordPromptAllowed = allowPasswordPrompt; passwordPromptAllowed = allowPasswordPrompt;
if (username != null) { if (username != null) {
defaultUserName = username; preferredName = username;
} }
// clear existing key store settings // clear existing key store settings
@ -247,45 +256,48 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
@Override @Override
public boolean processPasswordCallbacks(String title, String serverType, String serverName, public boolean processPasswordCallbacks(String title, String serverType, String serverName,
NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb,
AnonymousCallback anonymousCb, String loginError) { ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) {
if (anonymousCb != null && !passwordPromptAllowed) { if (anonymousCb != null && !passwordPromptAllowed) {
// Assume that login error will not occur with anonymous login // Assume that login error will not occur with anonymous login
anonymousCb.setAnonymousAccessRequested(true); anonymousCb.setAnonymousAccessRequested(true);
return true; return true;
} }
if (defaultUserName == null) {
throw new IllegalStateException("Default user name is unknown");
}
if (choiceCb != null) { if (choiceCb != null) {
choiceCb.setSelectedIndex(1); choiceCb.setSelectedIndex(1);
} }
String userName = null; String userName = null;
if (nameCb != null) { if (nameCb != null) {
userName = nameCb.getName(); if (allowUserNameEntry) {
if (userName == null) { // use preferred login name
userName = nameCb.getDefaultName(); userName = preferredName;
}
if (StringUtils.isBlank(userName)) {
// check for default login name in order of precedence
userName = nameCb.getName();
if (StringUtils.isBlank(userName)) {
userName = nameCb.getDefaultName();
}
}
if (allowUserNameEntry) {
nameCb.setName(userName);
} }
} }
if (userName == null) {
userName = defaultUserName;
}
if (nameCb != null) { if (!StringUtils.isBlank(userName)) {
nameCb.setName(defaultUserName); userName = ClientUtil.getUserName();
} }
String usage = null; String usage = null;
if (serverName != null) { if (serverName != null) {
usage = serverType + ": " + serverName; usage = serverType + ": " + serverName;
} }
// Ignore prompt specified by passCb // Ignore prompt specified by passCb
String prompt = "Password for " + userName +":"; String prompt = "Password for " + userName + ":";
char[] password = getPassword(usage, prompt); char[] password = getPassword(usage, prompt);
passCb.setPassword(password); passCb.setPassword(password);
return password != null; return password != null;
@ -321,7 +333,18 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator {
return false; return false;
} }
if (nameCb != null) { if (nameCb != null) {
nameCb.setName(defaultUserName); // presence of NameCallback implies user is allowed to specify name
String userName = preferredName;
if (StringUtils.isBlank(userName)) {
userName = nameCb.getName();
if (StringUtils.isBlank(userName)) {
userName = nameCb.getDefaultName();
}
if (!StringUtils.isBlank(userName)) {
userName = ClientUtil.getUserName();
}
}
nameCb.setName(userName);
} }
try { try {
sshCb.sign(sshPrivateKey); sshCb.sign(sshPrivateKey);

View file

@ -4,9 +4,9 @@
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -83,14 +83,24 @@ public class PasswordClientAuthenticator implements ClientAuthenticator {
} }
@Override @Override
public boolean processPasswordCallbacks(String title, String serverType, public boolean processPasswordCallbacks(String title, String serverType, String serverName,
String serverName, NameCallback nameCb, PasswordCallback passCb, boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb,
ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) { ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) {
if (choiceCb != null) { if (choiceCb != null) {
choiceCb.setSelectedIndex(1); choiceCb.setSelectedIndex(1);
} }
if (nameCb != null && username != null) { if (nameCb != null && allowUserNameEntry) {
nameCb.setName(username); String name = username;
if (name == null) {
name = nameCb.getName();
}
if (name == null) {
name = nameCb.getDefaultName();
}
if (name == null) {
name = ClientUtil.getUserName();
}
nameCb.setName(name);
} }
passCb.setPassword(password.clone()); passCb.setPassword(password.clone());
return true; return true;