Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility to remember password for shared databases. #1846

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/net/sf/jabref/cli/ArgumentProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import net.sf.jabref.model.database.BibDatabaseMode;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.preferences.JabRefPreferences;
import net.sf.jabref.shared.prefs.SharedDatabasePreferences;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
Expand Down Expand Up @@ -412,6 +413,7 @@ private void resetPreferences(String value) {
try {
System.out.println(Localization.lang("Setting all preferences to default values."));
Globals.prefs.clear();
new SharedDatabasePreferences().clear();
} catch (BackingStoreException e) {
System.err.println(Localization.lang("Unable to clear preferences."));
LOGGER.error("Unable to clear preferences", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import net.sf.jabref.logic.util.FileExtensions;
import net.sf.jabref.preferences.JabRefPreferences;
import net.sf.jabref.preferences.JabRefPreferencesFilter;
import net.sf.jabref.shared.prefs.SharedDatabasePreferences;

import com.jgoodies.forms.builder.ButtonBarBuilder;
import org.apache.commons.logging.Log;
Expand Down Expand Up @@ -197,6 +198,7 @@ public PreferencesDialog(JabRefFrame parent) {
Localization.lang("Reset preferences"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION) {
try {
prefs.clear();
new SharedDatabasePreferences().clear();
JOptionPane.showMessageDialog(PreferencesDialog.this,
Localization.lang("You must restart JabRef for this to come into effect."),
Localization.lang("Reset preferences"), JOptionPane.WARNING_MESSAGE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.sql.SQLException;
import java.util.Optional;
import java.util.Set;
Expand All @@ -15,6 +17,7 @@
import javax.swing.BorderFactory;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
Expand All @@ -40,9 +43,16 @@
import net.sf.jabref.shared.DBMSConnector;
import net.sf.jabref.shared.DBMSType;
import net.sf.jabref.shared.exception.DatabaseNotSupportedException;
import net.sf.jabref.shared.prefs.SharedDatabasePreferences;
import net.sf.jabref.shared.security.Password;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class OpenSharedDatabaseDialog extends JDialog {

private static final Log LOGGER = LogFactory.getLog(OpenSharedDatabaseDialog.class);

private final JabRefFrame frame;

private final GridBagLayout gridBagLayout = new GridBagLayout();
Expand All @@ -68,11 +78,9 @@ public class OpenSharedDatabaseDialog extends JDialog {
private final JButton cancelButton = new JButton(Localization.lang("Cancel"));
private final JButton helpButton = new HelpAction(HelpFile.SQL_DATABASE).getHelpButton();

private static final String SHARED_DATABASE_TYPE = "sharedDatabaseType";
private static final String SHARED_DATABASE_HOST = "sharedDatabaseHost";
private static final String SHARED_DATABASE_PORT = "sharedDatabasePort";
private static final String SHARED_DATABASE_NAME = "sharedDatabaseName";
private static final String SHARED_DATABASE_USER = "sharedDatabaseUser";
private final JCheckBox rememberPassword = new JCheckBox(Localization.lang("Remember password?"));

private final SharedDatabasePreferences prefs = new SharedDatabasePreferences();

private DBMSConnectionProperties connectionProperties;
private BibDatabaseContext bibDatabaseContext;
Expand All @@ -84,7 +92,7 @@ public OpenSharedDatabaseDialog(JabRefFrame frame) {
super(frame, Localization.lang("Open shared database"));
this.frame = frame;
initLayout();
applyGlobalPrefs();
applyPreferences();
setupActions();
pack();
setLocationRelativeTo(frame);
Expand All @@ -96,7 +104,7 @@ public void openSharedDatabase() {
try {
bibDatabaseContext.getDBSynchronizer().openSharedDatabase(connectionProperties);
frame.addTab(bibDatabaseContext, true);
setGlobalPrefs();
setPreferences();
bibDatabaseContext.getDBSynchronizer()
.registerListener(
new SharedDatabaseUIManager(frame, Globals.prefs.get(JabRefPreferences.KEYWORD_SEPARATOR)));
Expand Down Expand Up @@ -170,12 +178,14 @@ public void actionPerformed(ActionEvent e) {
/**
* Fetches possibly saved data and configures the control elements respectively.
*/
private void applyGlobalPrefs() {
Optional<String> sharedDatabaseType = Globals.prefs.getAsOptional(SHARED_DATABASE_TYPE);
Optional<String> sharedDatabaseHost = Globals.prefs.getAsOptional(SHARED_DATABASE_HOST);
Optional<String> sharedDatabasePort = Globals.prefs.getAsOptional(SHARED_DATABASE_PORT);
Optional<String> sharedDatabaseName = Globals.prefs.getAsOptional(SHARED_DATABASE_NAME);
Optional<String> sharedDatabaseUser = Globals.prefs.getAsOptional(SHARED_DATABASE_USER);
private void applyPreferences() {
Optional<String> sharedDatabaseType = prefs.getType();
Optional<String> sharedDatabaseHost = prefs.getHost();
Optional<String> sharedDatabasePort = prefs.getPort();
Optional<String> sharedDatabaseName = prefs.getName();
Optional<String> sharedDatabaseUser = prefs.getUser();
Optional<String> sharedDatabasePassword = prefs.getPassword();
boolean sharedDatabaseRememberPassword = prefs.getRememberPassword();

if (sharedDatabaseType.isPresent()) {
Optional<DBMSType> dbmsType = DBMSType.fromString(sharedDatabaseType.get());
Expand All @@ -201,6 +211,16 @@ private void applyGlobalPrefs() {
if (sharedDatabaseUser.isPresent()) {
userField.setText(sharedDatabaseUser.get());
}

if (sharedDatabasePassword.isPresent() && sharedDatabaseUser.isPresent()) {
try {
passwordField.setText(new Password(sharedDatabasePassword.get().toCharArray(), sharedDatabaseUser.get()).decrypt());
} catch (GeneralSecurityException | UnsupportedEncodingException e) {
LOGGER.error("Could not read the password due to decryption problems.", e);
}
}

rememberPassword.setSelected(sharedDatabaseRememberPassword);
}

/**
Expand Down Expand Up @@ -265,6 +285,9 @@ private void initLayout() {
gridBagConstraints.gridy = 4;
connectionPanel.add(passwordField, gridBagConstraints);

gridBagConstraints.gridy = 5;
connectionPanel.add(rememberPassword, gridBagConstraints);

// 3. column
gridBagConstraints.gridx = 2;
gridBagConstraints.gridy = 1;
Expand All @@ -274,7 +297,7 @@ private void initLayout() {

// help button
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 5;
gridBagConstraints.gridy = 6;
gridBagConstraints.insets = new Insets(10, 10, 0, 0);
JPanel helpPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
helpPanel.add(helpButton);
Expand Down Expand Up @@ -303,12 +326,24 @@ private void initLayout() {
/**
* Saves the data from this dialog persistently to facilitate the usage.
*/
public void setGlobalPrefs() {
Globals.prefs.put(SHARED_DATABASE_TYPE, ((DBMSType) dbmsTypeDropDown.getSelectedItem()).toString());
Globals.prefs.put(SHARED_DATABASE_HOST, hostField.getText());
Globals.prefs.put(SHARED_DATABASE_PORT, portField.getText());
Globals.prefs.put(SHARED_DATABASE_NAME, databaseField.getText());
Globals.prefs.put(SHARED_DATABASE_USER, userField.getText());
public void setPreferences() {
prefs.setType(((DBMSType) dbmsTypeDropDown.getSelectedItem()).toString());
prefs.setHost(hostField.getText());
prefs.setPort(portField.getText());
prefs.setName(databaseField.getText());
prefs.setUser(userField.getText());

if (rememberPassword.isSelected()) {
try {
prefs.setPassword(new Password(passwordField.getPassword(), userField.getText()).encrypt());
} catch (GeneralSecurityException | UnsupportedEncodingException e) {
LOGGER.error("Could not store the password due to encryption problems.", e);
}
} else {
prefs.clearPassword(); // for the case that the password is already set
}

prefs.setRememberPassword(rememberPassword.isSelected());
}

private boolean isEmptyField(JTextField field) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package net.sf.jabref.shared.prefs;

import java.util.Optional;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;

import net.sf.jabref.Globals;
import net.sf.jabref.JabRefMain;
import net.sf.jabref.gui.shared.OpenSharedDatabaseDialog;

/**
* Stores and reads persistent data for {@link OpenSharedDatabaseDialog}.
*/
public class SharedDatabasePreferences {

private static final String SHARED_DATABASE_TYPE = "sharedDatabaseType";
private static final String SHARED_DATABASE_HOST = "sharedDatabaseHost";
private static final String SHARED_DATABASE_PORT = "sharedDatabasePort";
private static final String SHARED_DATABASE_NAME = "sharedDatabaseName";
private static final String SHARED_DATABASE_USER = "sharedDatabaseUser";
private static final String SHARED_DATABASE_PASSWORD = "sharedDatabasePassword";
private static final String SHARED_DATABASE_REMEMBER_PASSWORD = "sharedDatabaseRememberPassword";

// This {@link Preferences} is used only for things which should not appear in real JabRefPreferences due to security reasons.
private final Preferences internalPrefs = Preferences.userNodeForPackage(JabRefMain.class).parent().node("jabref-pwdstorage");


public Optional<String> getType() {
return Globals.prefs.getAsOptional(SHARED_DATABASE_TYPE);
}

public Optional<String> getHost() {
return Globals.prefs.getAsOptional(SHARED_DATABASE_HOST);
}

public Optional<String> getPort() {
return Globals.prefs.getAsOptional(SHARED_DATABASE_PORT);
}

public Optional<String> getName() {
return Globals.prefs.getAsOptional(SHARED_DATABASE_NAME);
}

public Optional<String> getUser() {
return Globals.prefs.getAsOptional(SHARED_DATABASE_USER);
}

public Optional<String> getPassword() {
return Optional.ofNullable(internalPrefs.get(SHARED_DATABASE_PASSWORD, null));
}

public boolean getRememberPassword() {
return Globals.prefs.getBoolean(SHARED_DATABASE_REMEMBER_PASSWORD, false);
}

public void setType(String type) {
Globals.prefs.put(SHARED_DATABASE_TYPE, type);
}

public void setHost(String host) {
Globals.prefs.put(SHARED_DATABASE_HOST, host);
}

public void setPort(String port) {
Globals.prefs.put(SHARED_DATABASE_PORT, port);
}

public void setName(String name) {
Globals.prefs.put(SHARED_DATABASE_NAME, name);
}

public void setUser(String user) {
Globals.prefs.put(SHARED_DATABASE_USER, user);
}

public void setPassword(String password) {
internalPrefs.put(SHARED_DATABASE_PASSWORD, password);
}

public void setRememberPassword(boolean rememberPassword) {
Globals.prefs.putBoolean(SHARED_DATABASE_REMEMBER_PASSWORD, rememberPassword);
}

public void clearPassword() {
internalPrefs.remove(SHARED_DATABASE_PASSWORD);
}

public void clear() throws BackingStoreException {
internalPrefs.clear();
}

}
65 changes: 65 additions & 0 deletions src/main/java/net/sf/jabref/shared/security/Password.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package net.sf.jabref.shared.security;

import java.io.UnsupportedEncodingException;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Base64;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

/**
* {@link Password} contains methods which are useful to encrypt and decrypt passwords using symetric algorithms.
*/
public class Password {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a javadoc as this class might be usefull to others in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


private final byte[] phrase;
private final Cipher cipher;
private final SecretKeySpec secretKey;
private final IvParameterSpec ivSpec;


/**
* @param phrase Phrase which should be encrypted or decrypted
* @param key Key which is used to improve symmetric encryption
*/
public Password(char[] phrase, String key) throws NoSuchAlgorithmException, NoSuchPaddingException {
this.phrase = new String(phrase).getBytes();
this.cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
this.secretKey = new SecretKeySpec(get128BitHash(key.getBytes()), "AES");
this.ivSpec = new IvParameterSpec("ThisIsA128BitKey".getBytes());
}

/**
* Encrypts the set phrase/password with a symmetric encryption algorithm.
*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, AES 128 bit is a joke and not really secure, I personally would prefer at least 256biz, but using stronger encryption is only possible with the Cryptographic Extensions Policy and unfortunately those fall under the US import/export regulation laws.

It would be possible to check if they are installed and use a greater key size. Not sure it if is really necessary here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This functionality is unsecure anyway. Aim: #1846 (comment).

* @return Encrypted phrase/password
*/
public String encrypt() throws GeneralSecurityException, UnsupportedEncodingException {
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
return new String(Base64.getEncoder().encode(cipher.doFinal(phrase)), "UTF-8");
}

/**
* Decrypts the set phrase/password which was encrypted via {@link Password#encrypt()}.
*
* @return Decrypted phrase/password
*/
public String decrypt() throws GeneralSecurityException, UnsupportedEncodingException {
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
return new String(cipher.doFinal(Base64.getDecoder().decode(phrase)), "UTF-8");
}

/**
* Returns a 128 bit hash using SHA-256.
*/
private byte[] get128BitHash(byte[] byteArrayToHash) throws NoSuchAlgorithmException {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(byteArrayToHash);
return Arrays.copyOf(messageDigest.digest(), 16); // return 128 bit
}
}
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_da.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1775,3 +1775,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_de.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2487,3 +2487,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=Passwort_merken?
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2268,6 +2268,7 @@ Shared_version\:_%0=Shared_version\:_%0
Please_merge_the_shared_entry_with_yours_and_press_"Merge_entries"_to_resolve_this_problem.=Please_merge_the_shared_entry_with_yours_and_press_"Merge_entries"_to_resolve_this_problem.
Canceling_this_operation_will_leave_your_changes_unsynchronized._Cancel_anyway?=Canceling_this_operation_will_leave_your_changes_unsynchronized._Cancel_anyway?
The_BibEntry_you_currently_work_on_has_been_deleted_on_the_shared_side._Hit_"Keep"_to_recover_the_entry.=The_BibEntry_you_currently_work_on_has_been_deleted_on_the_shared_side._Hit_"Keep"_to_recover_the_entry.
Remember_password?=Remember_password?
Cannot_cite_entries_without_BibTeX_keys._Generate_keys_now?=Cannot_cite_entries_without_BibTeX_keys._Generate_keys_now?
New_technical_report=New_technical_report

Expand Down
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_es.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1676,3 +1676,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_fa.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2456,3 +2456,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
2 changes: 2 additions & 0 deletions src/main/resources/l10n/JabRef_fr.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1722,3 +1722,5 @@ Look_up_full_text_documents=Recherche_des_textes_intégraux
You_are_about_to_look_up_full_text_documents_for_%0_entries.=Vous_allez_rechercher_les_textes_intégraux_pour_%0_entrées.

last_four_nonpunctuation_characters_should_be_numerals=Les_4_derniers_caractères_devraient_être_des_chiffres

Remember_password?=
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_in.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1692,3 +1692,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_it.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1793,3 +1793,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_ja.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2433,3 +2433,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
1 change: 1 addition & 0 deletions src/main/resources/l10n/JabRef_nl.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2465,3 +2465,4 @@ Look_up_full_text_documents=
You_are_about_to_look_up_full_text_documents_for_%0_entries.=

last_four_nonpunctuation_characters_should_be_numerals=
Remember_password?=
Loading