IDEA-55298 Remember credentials when authenticating over HTTP

Unless paranoid, remember in memory always.
If "Remember password" is checked, remember in the password safe.
This commit is contained in:
Kirill Likhodedov
2011-11-14 19:41:56 +03:00
parent 24f63b0b63
commit 42a2cfa3b8
3 changed files with 224 additions and 19 deletions
@@ -15,8 +15,15 @@
*/
package git4idea.jgit;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.PasswordSafeException;
import com.intellij.ide.passwordSafe.config.PasswordSafeSettings;
import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl;
import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import git4idea.push.GitSimplePushResult;
import git4idea.remote.GitRememberedInputs;
import git4idea.repo.GitRepository;
import git4idea.update.GitFetchResult;
import org.eclipse.jgit.api.FetchCommand;
@@ -195,7 +202,17 @@ public final class GitHttpAdapter {
private static GeneralResult callWithAuthRetry(@NotNull MyRunnable command, GitHttpCredentialsProvider provider) throws InvalidRemoteException, IOException {
for (int i = 0; i < 3; i++) {
try {
AuthData authData = getUsernameAndPassword(provider.getProject(), provider.getUrl());
if (authData != null) {
provider.fillAuthDataIfNotFilled(authData.getLogin(), authData.getPassword());
}
if (i == 0) {
provider.setAlwaysShowDialog(false); // if username and password are supplied, no need to show the dialog
} else {
provider.setAlwaysShowDialog(true); // unless these values fail authentication
}
command.run();
rememberPassword(provider);
return GeneralResult.SUCCESS;
} catch (JGitInternalException e) {
if (!authError(e)) {
@@ -207,7 +224,98 @@ public final class GitHttpAdapter {
}
return GeneralResult.NOT_AUTHORIZED;
}
private static void rememberPassword(@NotNull GitHttpCredentialsProvider credentialsProvider) {
if (!credentialsProvider.wasDialogShown()) { // the dialog is not shown => everything is already stored
return;
}
final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
if (passwordSafe.getSettings().getProviderType() == PasswordSafeSettings.ProviderType.DO_NOT_STORE) {
return;
}
String login = credentialsProvider.getUserName();
if (login == null || credentialsProvider.getPassword() == null) {
return;
}
String url = adjustHttpUrl(credentialsProvider.getUrl());
String key = keyForUrlAndLogin(url, login);
try {
// store in memory always
storePassword(passwordSafe.getMemoryProvider(), credentialsProvider, key);
if (credentialsProvider.isRememberPassword()) {
storePassword(passwordSafe.getMasterKeyProvider(), credentialsProvider, key);
}
GitRememberedInputs.getInstance().addUrl(url, login);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't store the password for key [" + key + "]", e);
}
}
private static void storePassword(PasswordSafeProvider passwordProvider, GitHttpCredentialsProvider credentialsProvider, String key) throws PasswordSafeException {
passwordProvider.storePassword(credentialsProvider.getProject(), GitHttpCredentialsProvider.class, key, credentialsProvider.getPassword());
}
@Nullable
private static AuthData getUsernameAndPassword(Project project, String url) {
url = adjustHttpUrl(url);
String userName = GitRememberedInputs.getInstance().getUserNameForUrl(url);
if (userName == null) {
return null;
}
String key = keyForUrlAndLogin(url, userName);
final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
try {
String password = passwordSafe.getMemoryProvider().getPassword(project, GitHttpCredentialsProvider.class, key);
if (password == null) {
password = passwordSafe.getMasterKeyProvider().getPassword(project, GitHttpCredentialsProvider.class, key);
}
return password != null ? new AuthData(userName, password) : null;
}
catch (PasswordSafeException e) {
LOG.info("Couldn't store the password for key [" + key + "]", e);
return null;
}
}
private static class AuthData {
private final String myLogin;
private final String myPassword;
private AuthData(@NotNull String login, @NotNull String password) {
myPassword = password;
myLogin = login;
}
@NotNull
public String getLogin() {
return myLogin;
}
@NotNull
public String getPassword() {
return myPassword;
}
}
/**
* If url is HTTPS, store it as HTTP in the password database, not to make user enter and remember same credentials twice.
*/
@NotNull
private static String adjustHttpUrl(@NotNull String url) {
if (url.startsWith("https")) {
return url.replaceFirst("https", "http");
}
return url;
}
@NotNull
private static String keyForUrlAndLogin(@NotNull String stringUrl, @NotNull String login) {
return login + ":" + stringUrl;
}
private static boolean authError(@NotNull JGitInternalException e) {
Throwable cause = e.getCause();
return (cause instanceof TransportException && cause.getMessage().contains("not authorized"));
@@ -33,9 +33,17 @@ import java.util.regex.Pattern;
*/
public class GitHttpCredentialsProvider extends CredentialsProvider {
private static final Pattern HTTP_URL_PATTERN = Pattern.compile("http(?:s?)://(?:([\\S^@\\.]*)@)?.*");
private final Project myProject;
private final String myRemoteUrl;
private boolean myCancelled;
private boolean myRememberPassword;
private String myPassword;
private String myUserName;
private boolean myShowDialog;
private boolean myDialogShown;
public GitHttpCredentialsProvider(@NotNull Project project, @NotNull String remoteUrl) {
myProject = project;
@@ -75,16 +83,33 @@ public class GitHttpCredentialsProvider extends CredentialsProvider {
if (userNameItem != null || passwordItem != null) {
String username = getUserNameFromUrl(myRemoteUrl);
final AuthDialog dialog = new AuthDialog(myProject, "Login required", "Login to " + myRemoteUrl, username, null, true);
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
dialog.show();
}
});
boolean ok = dialog.isOK();
String password = null;
if (username == null) { // username is not in the url => reading pre-filled value from the password storage
username = myUserName;
password = myPassword;
} else if (username.equals(myUserName)) { // username is in url => read password only if it is for the same user
password = myPassword;
}
final AuthDialog dialog = new AuthDialog(myProject, "Login required", "Login to " + myRemoteUrl, username, password, false);
boolean ok;
if (username != null && password != null && !myShowDialog) {
ok = true;
myDialogShown = false;
} else {
UIUtil.invokeAndWaitIfNeeded(new Runnable() {
@Override
public void run() {
dialog.show();
}
});
ok = dialog.isOK();
myDialogShown = true;
}
if (!ok) {
myCancelled = true;
myRememberPassword = false; // in case of re-usage of the provider
} else {
if (userNameItem != null) {
userNameItem.setValue(dialog.getUsername());
@@ -92,14 +117,56 @@ public class GitHttpCredentialsProvider extends CredentialsProvider {
if (passwordItem != null) {
passwordItem.setValue(dialog.getPassword().toCharArray());
}
myRememberPassword = dialog.isRememberPassword();
myPassword = dialog.getPassword();
myUserName = dialog.getUsername();
}
return ok;
}
return true;
}
private static final Pattern HTTP_URL_PATTERN = Pattern.compile("http(?:s?)://(?:([\\S^@\\.]*)@)?.*");
public boolean isRememberPassword() {
return myRememberPassword;
}
@NotNull
public Project getProject() {
return myProject;
}
@Nullable
public String getPassword() {
return myPassword;
}
@Nullable
public String getUserName() {
return myUserName;
}
@NotNull
public String getUrl() {
return myRemoteUrl;
}
public void fillAuthDataIfNotFilled(@NotNull String login, @NotNull String password) {
if (myUserName == null) {
myUserName = login;
myPassword = password;
} else if (myPassword != null) {
myPassword = password;
}
}
public void setAlwaysShowDialog(boolean showDialog) {
myShowDialog = showDialog;
}
public boolean wasDialogShown() {
return myDialogShown;
}
@Nullable
private static String getUserNameFromUrl(@NotNull String url) {
Matcher matcher = HTTP_URL_PATTERN.matcher(url);
@@ -19,6 +19,8 @@ import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -34,6 +36,16 @@ public class GitRememberedInputs implements PersistentStateComponent<GitRemember
private State myState = new State();
public static class State {
public List<UrlAndUserName> visitedUrls = new ArrayList<UrlAndUserName>();
public String cloneParentDir = "";
}
public static class UrlAndUserName {
public String url;
public String userName;
}
public static GitRememberedInputs getInstance() {
return ServiceManager.getService(GitRememberedInputs.class);
}
@@ -48,24 +60,42 @@ public class GitRememberedInputs implements PersistentStateComponent<GitRemember
myState = state;
}
public void addUrl(String url) {
myState.myVisitedUrls.add(url);
public void addUrl(@NotNull String url) {
addUrl(url, "");
}
public void addUrl(@NotNull String url, @NotNull String userName) {
UrlAndUserName urlAndUserName = new UrlAndUserName();
urlAndUserName.url = url;
urlAndUserName.userName = userName;
myState.visitedUrls.add(urlAndUserName);
}
@Nullable
public String getUserNameForUrl(String url) {
for (UrlAndUserName urlAndUserName : myState.visitedUrls) {
if (urlAndUserName.url.equalsIgnoreCase(url)) {
return urlAndUserName.userName;
}
}
return null;
}
@NotNull
public List<String> getVisitedUrls() {
return myState.myVisitedUrls;
List<String> urls = new ArrayList<String>(myState.visitedUrls.size());
for (UrlAndUserName urlAndUserName : myState.visitedUrls) {
urls.add(urlAndUserName.url);
}
return urls;
}
public String getCloneParentDir() {
return myState.myCloneParentDir;
return myState.cloneParentDir;
}
public void setCloneParentDir(String cloneParentDir) {
myState.myCloneParentDir = cloneParentDir;
myState.cloneParentDir = cloneParentDir;
}
public static class State {
public List<String> myVisitedUrls = new ArrayList<String>();
public String myCloneParentDir = "";
}
}