mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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 = "";
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user