From d0badde92b723e213c27120f21bbe6954b4fd2cf Mon Sep 17 00:00:00 2001 From: ghidra1 Date: Thu, 13 Feb 2025 14:11:30 -0500 Subject: [PATCH] GP-5362 Correct password entry bug and refactor PasswordDialog/ClientAuthenticator --- .../dialog/CreateBsimServerInfoDialog.java | 4 - .../BSimPostgresDBConnectionManager.java | 6 +- .../features/bsim/query/BSimServerInfo.java | 2 +- .../java/ghidra/app/script/GhidraScript.java | 9 +- .../java/docking/widgets/PasswordDialog.java | 104 +++++++++++++----- .../PopupKeyStorePasswordProvider.java | 7 +- .../docking/framework/SplashScreenTest.java | 8 +- .../framework/client/ClientAuthenticator.java | 11 +- .../ghidra/framework/client/ClientUtil.java | 7 +- .../client/DefaultClientAuthenticator.java | 39 ++++--- .../client/HeadlessClientAuthenticator.java | 91 +++++++++------ .../client/PasswordClientAuthenticator.java | 22 +++- 12 files changed, 194 insertions(+), 116 deletions(-) diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java index d9c7ca451d..96725f0eab 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/gui/search/dialog/CreateBsimServerInfoDialog.java @@ -418,10 +418,6 @@ public class CreateBsimServerInfoDialog extends DialogComponentProvider { } String user = userField.getText().trim(); - if (ClientUtil.getUserName().equals(user)) { - user = null; - } - String name = nameField.getText().trim(); String host = hostField.getText().trim(); diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java index 6728f09201..ae013ea07b 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimPostgresDBConnectionManager.java @@ -334,14 +334,16 @@ public class BSimPostgresDBConnectionManager { throw new SQLException("No registered authenticator"); } NameCallback nameCb = new NameCallback("User ID:", bds.getUsername()); + boolean allowUserIDEntry = true; if (!serverInfo.hasDefaultLogin()) { nameCb.setName(bds.getUsername()); + allowUserIDEntry = false; } PasswordCallback passCb = new PasswordCallback(" ", false); // force use of default prompting try { if (!clientAuthenticator.processPasswordCallbacks( - "BSim Database Authentication", "BSim Database Server", - serverInfo.toString(), nameCb, passCb, null, null, loginError)) { + "BSim Database Authentication", "BSim DB Server", serverInfo.toString(), + allowUserIDEntry, nameCb, passCb, null, null, loginError)) { throw new CancelledException(); } bds.setPassword(new String(passCb.getPassword())); diff --git a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java index 05ccc40925..6960e62cee 100644 --- a/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java +++ b/Ghidra/Features/BSim/src/main/java/ghidra/features/bsim/query/BSimServerInfo.java @@ -376,7 +376,7 @@ public class BSimServerInfo implements Comparable { } /** - * 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 */ public boolean hasDefaultLogin() { diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java index bdc31cc6e7..42cca79568 100644 --- a/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java +++ b/Ghidra/Features/Base/src/main/java/ghidra/app/script/GhidraScript.java @@ -590,8 +590,8 @@ public abstract class GhidraScript extends FlatProgramAPI { if (isRunningHeadless()) { // only change client authenticator in headless mode try { - HeadlessClientAuthenticator.installHeadlessClientAuthenticator( - ClientUtil.getUserName(), null, false); + HeadlessClientAuthenticator + .installHeadlessClientAuthenticator(ClientUtil.getUserName(), null, false); } catch (IOException 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 -> { - AskAddrDialog dialog = - new AskAddrDialog(title, message, currentProgram, lastValue); + AskAddrDialog dialog = new AskAddrDialog(title, message, currentProgram, lastValue); if (dialog.isCanceled()) { throw new CancelledException(); } @@ -3158,7 +3157,7 @@ public abstract class GhidraScript extends FlatProgramAPI { throw new ImproperUseException( "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 { state.getTool().showDialog(dialog); if (!dialog.okWasPressed()) { diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java index 43d06e5cc7..10239b5d22 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PasswordDialog.java @@ -20,6 +20,8 @@ import java.awt.event.*; import javax.swing.*; +import org.apache.commons.lang3.StringUtils; + import docking.DialogComponentProvider; import docking.widgets.checkbox.GCheckBox; import docking.widgets.combobox.GComboBox; @@ -39,26 +41,34 @@ public class PasswordDialog extends DialogComponentProvider { private JComboBox choiceCB; private JCheckBox anonymousAccess; 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 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, 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 - * @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 + * @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 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 defaultChoice default choice index * @param includeAnonymousOption true signals to add a checkbox to request anonymous login */ public PasswordDialog(String title, String serverType, String serverName, String passPrompt, - String userIdPrompt, String defaultUserID, String choicePrompt, String[] choices, - int defaultChoice, boolean includeAnonymousOption) { - this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID); + boolean allowUserIdEntry, String userIdPrompt, String defaultUserId, + String choicePrompt, String[] choices, int defaultChoice, + boolean includeAnonymousOption) { + this(title, serverType, serverName, passPrompt, allowUserIdEntry, userIdPrompt, + defaultUserId, true); if (choicePrompt != null) { workPanel.add(new GLabel(choicePrompt)); 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 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, in which case - * "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 defaultUserID default name when prompting for a name + * "Password:" is prompt. */ - public PasswordDialog(String title, String serverType, String serverName, String passPrompt, - String userIdPrompt, String defaultUserID) { - this(title, serverType, serverName, passPrompt, userIdPrompt, defaultUserID, true); + public PasswordDialog(String title, String serverType, String serverName, String passPrompt) { + this(title, serverType, serverName, passPrompt, 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 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, in which case * "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 defaultUserID default name when prompting for a name - * @param hasMessages true if the client will set messages on this dialog. If true, the - * dialog's minimum size will be increased + * @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, - 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); - this.defaultUserID = defaultUserID; + this.defaultUserId = defaultUserId; setRememberSize(false); setTransient(true); if (hasMessages) { - setMinimumSize(300, 150); + setMinimumSize(350, 150); } workPanel = new JPanel(new PairLayout(5, 5)); @@ -136,20 +173,27 @@ public class PasswordDialog extends DialogComponentProvider { 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)); - nameField = new JTextField(defaultUserID, 16); + nameField = new JTextField(defaultUserId, 16); nameField.setName("NAME-ENTRY-COMPONENT"); workPanel.add(nameField); } - else if (defaultUserID != null) { - workPanel.add(new GLabel("User ID:")); - JLabel nameLabel = new GLabel(defaultUserID); + else if (defaultUserId != null) { + workPanel.add(new GLabel(userIdPrompt)); + JLabel nameLabel = new GLabel(defaultUserId); nameLabel.setName("NAME-COMPONENT"); workPanel.add(nameLabel); } - workPanel.add(new GLabel(passPrompt != null ? passPrompt : "Password:")); + workPanel.add(new GLabel(passPrompt)); passwordField = new JPasswordField(16); passwordField.setName("PASSWORD-ENTRY-COMPONENT"); workPanel.add(passwordField); @@ -245,7 +289,7 @@ public class PasswordDialog extends DialogComponentProvider { * @return the user ID / Name entered in the password field */ public String getUserID() { - return nameField != null ? nameField.getText().trim() : defaultUserID; + return nameField != null ? nameField.getText().trim() : defaultUserId; } /** diff --git a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupKeyStorePasswordProvider.java b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupKeyStorePasswordProvider.java index 002fe5c995..1d141e9e74 100644 --- a/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupKeyStorePasswordProvider.java +++ b/Ghidra/Framework/Docking/src/main/java/docking/widgets/PopupKeyStorePasswordProvider.java @@ -1,13 +1,12 @@ /* ### * IP: GHIDRA - * REVIEWED: YES * * Licensed 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. @@ -63,7 +62,7 @@ public class PopupKeyStorePasswordProvider implements KeyStorePasswordProvider { @Override public void run() { PasswordDialog pwdDialog = - new PasswordDialog("Protected PKI Certificate", "Cert File", file, null, null, null); + new PasswordDialog("Protected PKI Certificate", "Cert File", file, null); if (passwordError) { pwdDialog.setErrorText("Incorrect password"); } diff --git a/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java b/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java index 8a6e0d4e78..ef4669a317 100644 --- a/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java +++ b/Ghidra/Framework/Docking/src/test.slow/java/docking/framework/SplashScreenTest.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -139,8 +139,8 @@ public class SplashScreenTest extends AbstractDockingTest { private DockingDialog showModalPasswordDialog(Frame parentFrame) throws Exception { String dialogTitle = "InfoWindowTest.testSplashScreenPasswordModality() Dialog"; - DialogComponentProvider passwordDialog = runSwing(() -> new PasswordDialog(dialogTitle, - "Server Type", "Server Name", "Prompt", null, null)); + DialogComponentProvider passwordDialog = + runSwing(() -> new PasswordDialog(dialogTitle, "Server Type", "Server Name", "Prompt")); if (parentFrame == null) { // null means to share the parent diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientAuthenticator.java index 7e123e3598..73172132fe 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientAuthenticator.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientAuthenticator.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -33,10 +33,11 @@ public interface ClientAuthenticator extends KeyStorePasswordProvider { public Authenticator getAuthenticator(); /** - * Process Ghidra Server password authentication callbacks. + * Process password authentication callbacks. * @param title password prompt title if GUI is used * @param serverType type of server (label associated with serverName) * @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 * that the default user name will be used, @see ClientUtil#getUserName() * @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 */ public boolean processPasswordCallbacks(String title, String serverType, String serverName, - NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, - AnonymousCallback anonymousCb, String loginError); + boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb, + ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError); /** * Prompt user for reconnect diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java index e7a423b44c..ab6bc6ebb1 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/ClientUtil.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -425,7 +425,8 @@ public class ClientUtil { "Unsupported authentication callback: " + callbacks[0].getClass().getName()); } 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; } String name = defaultUserID; diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java index 8f5a3f3d18..413a513b05 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/DefaultClientAuthenticator.java @@ -72,13 +72,14 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider if (pwd != null) { // Requesting URL specified password - return new PasswordAuthentication(userName, pwd.toCharArray()); + return new PasswordAuthentication(userName, pwd.toCharArray()); } - + NameCallback nameCb = new NameCallback("Name: ", userName); + boolean allowUserIDEntry = true; if (!useDefaultUser) { - // Prevent modification of user name by password prompting nameCb.setName(userName); + allowUserIDEntry = false; } // Prompt for password @@ -89,10 +90,10 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider PasswordCallback passCb = new PasswordCallback(prompt, false); try { ServerPasswordPrompt pp = new ServerPasswordPrompt("Connection Authentication", - "Server", serverName, nameCb, passCb, null, null, null); + "Server", serverName, allowUserIDEntry, nameCb, passCb, null, null, null); SystemUtilities.runSwingNow(pp); if (pp.okWasPressed()) { - return new PasswordAuthentication(nameCb.getName(), passCb.getPassword()); + return new PasswordAuthentication(nameCb.getName(), passCb.getPassword()); } } finally { @@ -135,10 +136,10 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider @Override public boolean processPasswordCallbacks(String title, String serverType, String serverName, - NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, - AnonymousCallback anonymousCb, String loginError) { - ServerPasswordPrompt pp = new ServerPasswordPrompt(title, serverType, serverName, nameCb, - passCb, choiceCb, anonymousCb, loginError); + boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb, + ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) { + ServerPasswordPrompt pp = new ServerPasswordPrompt(title, serverType, serverName, + allowUserNameEntry, nameCb, passCb, choiceCb, anonymousCb, loginError); SystemUtilities.runSwingNow(pp); return pp.okWasPressed(); } @@ -172,6 +173,7 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider private String title; private String serverType; // label for serverName field private String serverName; + private boolean allowUserIDEntry; private NameCallback nameCb; private PasswordCallback passCb; private ChoiceCallback choiceCb; @@ -180,11 +182,12 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider private boolean okPressed = false; ServerPasswordPrompt(String title, String serverType, String serverName, - NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, - AnonymousCallback anonymousCb, String errorMsg) { + boolean allowUserIDEntry, NameCallback nameCb, PasswordCallback passCb, + ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String errorMsg) { this.title = title; this.serverType = serverType; this.serverName = serverName; + this.allowUserIDEntry = allowUserIDEntry && (nameCb != null); this.nameCb = nameCb; this.passCb = passCb; this.choiceCb = choiceCb; @@ -225,20 +228,20 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider String defaultUserName = null; String namePrompt = null; if (nameCb != null) { + namePrompt = nameCb.getPrompt(); defaultUserName = nameCb.getName(); - if (defaultUserName == null) { - // Name entry only permitted with name callback where name has not be pre-set + if (StringUtils.isBlank(defaultUserName)) { defaultUserName = nameCb.getDefaultName(); - namePrompt = nameCb.getPrompt(); } } - if (defaultUserName == null) { + + if (StringUtils.isBlank(defaultUserName)) { defaultUserName = getDefaultUserName(); } PasswordDialog pwdDialog = new PasswordDialog(title, serverType, serverName, - passCb.getPrompt(), namePrompt, defaultUserName, choicePrompt, choices, - getDefaultChoice(), anonymousCb != null); + passCb.getPrompt(), allowUserIDEntry, namePrompt, defaultUserName, choicePrompt, + choices, getDefaultChoice(), anonymousCb != null); if (errorMsg != null) { pwdDialog.setErrorText(errorMsg); @@ -252,7 +255,7 @@ public class DefaultClientAuthenticator extends PopupKeyStorePasswordProvider } else { passCb.setPassword(pwdDialog.getPassword()); - if (nameCb != null) { + if (nameCb != null && allowUserIDEntry) { String username = pwdDialog.getUserID(); nameCb.setName(username); Preferences.setProperty(NAME_PREFERENCE, username); diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java index 2eaccec7e0..02d5b110af 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/HeadlessClientAuthenticator.java @@ -39,22 +39,25 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator { private final static char[] BADPASSWORD = "".toCharArray(); private static Object sshPrivateKey; - private static String defaultUserName = ClientUtil.getUserName(); + private static String preferredName = null; 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() { @Override protected PasswordAuthentication getPasswordAuthentication() { - - if (defaultUserName == null) { - throw new IllegalStateException("Default user name is unknown"); - } String serverName = getRequestingHost(); URL requestingURL = getRequestingURL(); // may be null String pwd = null; - String userName = defaultUserName; + String name = preferredName; if (requestingURL != null) { String userInfo = requestingURL.getUserInfo(); @@ -62,12 +65,12 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator { // Use user info from URL int pwdSep = userInfo.indexOf(':'); if (pwdSep < 0) { - userName = userInfo; + name = userInfo; } else { pwd = userInfo.substring(pwdSep + 1); 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(); } } - + Msg.debug(this, "PasswordAuthentication requested for " + serverName); + if (StringUtils.isBlank(name)) { + throw new IllegalStateException("Connection user name is unknown"); + } + if (pwd != null) { // Requesting URL specified password - return new PasswordAuthentication(userName, pwd.toCharArray()); + return new PasswordAuthentication(name, pwd.toCharArray()); } String usage = "Access password requested for " + serverName; String prompt = getRequestingPrompt(); 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 * allows username to be specified. If null, {@link ClientUtil#getUserName()} * will be used. @@ -118,7 +127,7 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator { boolean allowPasswordPrompt) throws IOException { passwordPromptAllowed = allowPasswordPrompt; if (username != null) { - defaultUserName = username; + preferredName = username; } // clear existing key store settings @@ -247,45 +256,48 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator { @Override public boolean processPasswordCallbacks(String title, String serverType, String serverName, - NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, - AnonymousCallback anonymousCb, String loginError) { + boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb, + ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) { if (anonymousCb != null && !passwordPromptAllowed) { // Assume that login error will not occur with anonymous login anonymousCb.setAnonymousAccessRequested(true); return true; } - - if (defaultUserName == null) { - throw new IllegalStateException("Default user name is unknown"); - } - + if (choiceCb != null) { choiceCb.setSelectedIndex(1); } - + String userName = null; if (nameCb != null) { - userName = nameCb.getName(); - if (userName == null) { - userName = nameCb.getDefaultName(); + if (allowUserNameEntry) { + // use preferred login name + 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) { - nameCb.setName(defaultUserName); + if (!StringUtils.isBlank(userName)) { + userName = ClientUtil.getUserName(); } String usage = null; if (serverName != null) { usage = serverType + ": " + serverName; } - + // Ignore prompt specified by passCb - String prompt = "Password for " + userName +":"; - + String prompt = "Password for " + userName + ":"; + char[] password = getPassword(usage, prompt); passCb.setPassword(password); return password != null; @@ -321,7 +333,18 @@ public class HeadlessClientAuthenticator implements ClientAuthenticator { return false; } 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 { sshCb.sign(sshPrivateKey); diff --git a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java index 2a22c4492d..33cc27e35a 100644 --- a/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java +++ b/Ghidra/Framework/FileSystem/src/main/java/ghidra/framework/client/PasswordClientAuthenticator.java @@ -4,9 +4,9 @@ * Licensed 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. @@ -83,14 +83,24 @@ public class PasswordClientAuthenticator implements ClientAuthenticator { } @Override - public boolean processPasswordCallbacks(String title, String serverType, - String serverName, NameCallback nameCb, PasswordCallback passCb, + public boolean processPasswordCallbacks(String title, String serverType, String serverName, + boolean allowUserNameEntry, NameCallback nameCb, PasswordCallback passCb, ChoiceCallback choiceCb, AnonymousCallback anonymousCb, String loginError) { if (choiceCb != null) { choiceCb.setSelectedIndex(1); } - if (nameCb != null && username != null) { - nameCb.setName(username); + if (nameCb != null && allowUserNameEntry) { + 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()); return true;