/*
*
* Copyright 2014 http://Bither.net
*
* 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.
* See the License for the specific language governing permissions and
* limitations under the License.
* /
*/
package net.bither.viewsystem.froms;
import net.bither.BitherSetting;
import net.bither.bitherj.crypto.PasswordSeed;
import net.bither.bitherj.crypto.SecureCharSequence;
import net.bither.bitherj.delegate.IPasswordGetter;
import net.bither.bitherj.delegate.IPasswordGetterDelegate;
import net.bither.bitherj.utils.Utils;
import net.bither.fonts.AwesomeIcon;
import net.bither.languages.MessageKey;
import net.bither.model.Check;
import net.bither.preference.UserPreference;
import net.bither.utils.CheckUtil;
import net.bither.utils.LocaliserUtils;
import net.bither.utils.PasswordStrengthUtil;
import net.bither.viewsystem.TextBoxes;
import net.bither.viewsystem.base.Labels;
import net.bither.viewsystem.base.Panels;
import net.bither.viewsystem.dialogs.DialogConfirmTask;
import net.bither.viewsystem.dialogs.MessageDialog;
import net.bither.viewsystem.listener.ICheckPasswordListener;
import net.bither.viewsystem.listener.IDialogPasswordListener;
import net.miginfocom.swing.MigLayout;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class PasswordPanel extends WizardPanel {
private JLabel verificationStatusLabel;
private JPasswordField newPassword;
private JPasswordField repeatNewPassword;
private JLabel labPassword;
private JLabel labConfirmPassword;
private PasswordSeed passwordSeed;
private IDialogPasswordListener listener;
private ICheckPasswordListener checkPasswordListener;
private boolean passwordEntered = false;
private boolean checkPre = true;
private ExecutorService executor;
private boolean etPasswordConfirmIsVisible = false;
private JProgressBar pb;
private JLabel labStrength;
private JPanel progressPanel;
private JPanel newPasswordPanel;
public PasswordPanel(IDialogPasswordListener dialogPasswordListener) {
super(MessageKey.SHOW_CHANGE_PASSWORD_WIZARD, AwesomeIcon.LOCK);
updateTitle(LocaliserUtils.getString("import_private_key_qr_code_password"));
this.listener = dialogPasswordListener;
passwordSeed = getPasswordSeed();
progressPanel = getProgressPanel();
newPasswordPanel = getNewPasswordPanel();
setOkAction(new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
onOK();
}
});
}
private PasswordSeed getPasswordSeed() {
return PasswordSeed.getPasswordSeed();
}
@Override
public void initialiseContent(JPanel panel) {
panel.setLayout(new MigLayout(
Panels.migXYLayout(),
"[]", // Column constraints
"[][]" // Row constraints
));
panel.add(progressPanel, "align center,shrink,wrap");
panel.add(newPasswordPanel, "wrap");
if (PasswordSeed.hasPasswordSeed()) {
labConfirmPassword.setVisible(false);
repeatNewPassword.setVisible(false);
}
passwordCheck.setCheckListener(passwordCheckListener);
newPassword.addKeyListener(passwordWatcher);
repeatNewPassword.addKeyListener(passwordWatcher);
configureCheckPre();
showCheckPre();
}
@Override
public void showPanel() {
super.showPanel();
newPassword.requestFocus();
}
private JPanel getProgressPanel() {
JPanel pbPanel = Panels.newPanel();
pbPanel.setLayout(new MigLayout(
Panels.migXYLayout(),
"[]", // Column constraints
"[][]" // Row constraints
));
pb = new JProgressBar();
Painter p = new Painter() {
@Override
public void paint(Graphics2D g, Object object, int width, int height) {
JProgressBar bar = (JProgressBar) object;
g.setColor(bar.getForeground());
g.fillRect(0, 1, width - 2, height - 2);
}
};
// install custom painter on the bar
UIDefaults properties = new UIDefaults();
properties.put("ProgressBar[Enabled].foregroundPainter", p);
pb.setBorderPainted(false);
pb.putClientProperty("Nimbus.Overrides", properties);
pb.setStringPainted(false);
pb.setMaximum(5);
pb.setVisible(false);
labStrength = Labels.newValueLabel("");
pbPanel.add(pb, "align center,shrink");
pbPanel.add(labStrength, "align center,shrink");
return pbPanel;
}
private JPanel getNewPasswordPanel() {
JPanel panel = Panels.newPanel(
new MigLayout(
Panels.migXLayout(), // Layout
"[][][][]", // Columns (require 4 columns for alignment with EnterPasswordView)
"[][][]" // Rows
));
newPassword = TextBoxes.newPassword();
newPassword.setName(MessageKey.ENTER_NEW_PASSWORD.getKey());
repeatNewPassword = TextBoxes.newPassword();
repeatNewPassword.setName(MessageKey.RETYPE_NEW_PASSWORD.getKey());
// Bind a document listener to allow instant update of UI to matched passwords
newPassword.getDocument().addDocumentListener(
new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
updateModel();
}
@Override
public void removeUpdate(DocumentEvent e) {
updateModel();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateModel();
}
private void updateModel() {
if (!repeatNewPassword.isVisible()) {
return;
}
if (!pb.isVisible()) {
pb.setVisible(true);
}
SecureCharSequence secureCharSequence = new SecureCharSequence(newPassword.getPassword());
PasswordStrengthUtil.PasswordStrength strength = PasswordStrengthUtil.checkPassword
(secureCharSequence);
pb.setValue(strength.getValue() + 1);
pb.setForeground(strength.getColor());
labStrength.setText(strength.getName());
secureCharSequence.wipe();
}
}
);
// Bind a document listener to allow instant update of UI to matched passwords
repeatNewPassword.getDocument().addDocumentListener(
new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
updateModel();
}
@Override
public void removeUpdate(DocumentEvent e) {
updateModel();
}
@Override
public void changedUpdate(DocumentEvent e) {
updateModel();
}
private void updateModel() {
}
});
verificationStatusLabel = Labels.newVerificationStatus(".credentials", true);
verificationStatusLabel.setVisible(false);
labPassword = Labels.newEnterPassword();
panel.add(labPassword);
panel.add(newPassword, " wrap");
labConfirmPassword = Labels.newRetypeNewPassword();
panel.add(labConfirmPassword);
panel.add(repeatNewPassword, "wrap");
panel.add(verificationStatusLabel, "span 4,grow,push");
return panel;
}
private KeyListener passwordWatcher = new KeyListener() {
private SecureCharSequence password;
private SecureCharSequence passwordConfirm;
@Override
public void keyTyped(KeyEvent keyEvent) {
}
@Override
public void keyPressed(KeyEvent keyEvent) {
if (password != null) {
password.wipe();
}
if (passwordConfirm != null) {
passwordConfirm.wipe();
}
password = new SecureCharSequence(newPassword.getPassword());
passwordConfirm = new SecureCharSequence(repeatNewPassword.getPassword());
}
@Override
public void keyReleased(KeyEvent keyEvent) {
SecureCharSequence p = new SecureCharSequence(newPassword.getPassword());
if (p.length() > 0) {
if (!Utils.validPassword(p)) {
newPassword.setText(password.toString());
}
}
p.wipe();
if (repeatNewPassword.isVisible()) {
SecureCharSequence pc = new SecureCharSequence(repeatNewPassword.getPassword());
if (pc.length() > 0) {
if (!Utils.validPassword(pc)) {
repeatNewPassword.setText(passwordConfirm.toString());
}
}
pc.wipe();
}
checkValid();
if (password != null) {
password.wipe();
}
if (passwordConfirm != null) {
passwordConfirm.wipe();
}
}
};
private void onOK() {
SecureCharSequence password = new SecureCharSequence(newPassword.getPassword());
SecureCharSequence passwordConfirm = new SecureCharSequence(repeatNewPassword.getPassword());
if (password == null || password.length() == 0) {
return;
}
if (passwordSeed == null && !password.equals(passwordConfirm) && checkPre) {
password.wipe();
passwordConfirm.wipe();
new MessageDialog((LocaliserUtils.getString
("add_address_generate_address_password_not_same"))).showMsg();
repeatNewPassword.requestFocus();
return;
}
PasswordStrengthUtil.PasswordStrength strength = PasswordStrengthUtil.checkPassword
(password);
password.wipe();
passwordConfirm.wipe();
if (UserPreference.getInstance().getCheckPasswordStrength() && repeatNewPassword.isVisible()) {
if (strength == PasswordStrengthUtil.PasswordStrength.Weak) {
String msg = Utils.format(LocaliserUtils.getString("password_strength_error"), strength.getName());
new MessageDialog(msg).showMsg();
return;
} else if (strength == PasswordStrengthUtil.PasswordStrength.Normal) {
String msg = Utils.format(LocaliserUtils.getString("password_strength_warning"), strength.getName());
DialogConfirmTask dialogConfirmTask = new DialogConfirmTask(msg, new Runnable() {
@Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
confirmPassword();
}
});
}
});
dialogConfirmTask.pack();
dialogConfirmTask.setVisible(true);
return;
}
}
confirmPassword();
}
private void confirmPassword() {
if ((passwordSeed != null && checkPre) || checkPasswordListener != null) {
ArrayList<Check> checks = new ArrayList<Check>();
checks.add(passwordCheck);
executor = CheckUtil.runChecks(checks, 1);
} else {
passwordEntered = true;
closePanel();
}
}
private void configureCheckPre() {
if (checkPre) {
if (passwordSeed != null) {
etPasswordConfirmIsVisible = false;
repeatNewPassword.setVisible(false);
labConfirmPassword.setVisible(false);
} else {
etPasswordConfirmIsVisible = true;
repeatNewPassword.setVisible(true);
labConfirmPassword.setVisible(true);
}
} else {
etPasswordConfirmIsVisible = false;
repeatNewPassword.setVisible(false);
labConfirmPassword.setVisible(false);
}
}
private void checkValid() {
setOkEnabled(false);
int passwordLength = newPassword.getPassword().length;
if (passwordLength >= BitherSetting.PASSWORD_LENGTH_MIN && passwordLength <= BitherSetting.PASSWORD_LENGTH_MAX) {
if (etPasswordConfirmIsVisible) {
int passwordConfirmLength = repeatNewPassword.getPassword().length;
if (passwordConfirmLength >= BitherSetting.PASSWORD_LENGTH_MIN && passwordConfirmLength <= BitherSetting.PASSWORD_LENGTH_MAX) {
setOkEnabled(true);
} else {
setOkEnabled(false);
}
} else {
setOkEnabled(true);
}
}
}
private void shake() {
}
public void setCheckPre(boolean check) {
checkPre = check;
configureCheckPre();
showCheckPre();
}
public void setCheckPasswordListener(ICheckPasswordListener checkPasswordListener) {
this.checkPasswordListener = checkPasswordListener;
}
public void showCheckPre() {
if (checkPre) {
if (etPasswordConfirmIsVisible) {
updateTitle(LocaliserUtils.getString("add_address_generate_address_password_set_label"));
} else {
updateTitle(LocaliserUtils.getString("add_address_generate_address_password_label"));
}
}
}
private Check.CheckListener passwordCheckListener = new Check.CheckListener() {
@Override
public void onCheckBegin(Check check) {
// pb.setVisible(true);
}
@Override
public void onCheckEnd(Check check, boolean success) {
if (executor != null) {
executor.shutdown();
executor = null;
}
if (success) {
passwordEntered = true;
closePanel();
} else {
newPassword.setText("");
checkValid();
new MessageDialog(LocaliserUtils.getString("password_wrong")).showMsg();
shake();
}
}
};
public void setTitle(String title) {
updateTitle(title);
}
private Check passwordCheck = new Check("", new Check.ICheckAction() {
@Override
public boolean check() {
SecureCharSequence password = new SecureCharSequence(newPassword.getPassword());
if (checkPasswordListener != null) {
boolean result = checkPasswordListener.checkPassword(password);
password.wipe();
return result;
} else if (passwordSeed != null) {
boolean result = passwordSeed.checkPassword(password);
password.wipe();
return result;
} else {
return true;
}
}
});
@Override
public void closePanel() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
PasswordPanel.super.closePanel();
if (listener != null) {
if (passwordEntered) {
listener.onPasswordEntered(new SecureCharSequence(newPassword.getPassword()));
newPassword.setText("");
repeatNewPassword.setText("");
} else {
listener.onPasswordEntered(null);
}
}
}
});
}
public static final class PasswordGetter implements IDialogPasswordListener, IPasswordGetter {
private ReentrantLock getPasswordLock = new ReentrantLock();
private Condition withPasswordCondition = getPasswordLock.newCondition();
private SecureCharSequence password;
private IPasswordGetterDelegate delegate;
public PasswordGetter() {
this(null);
}
public PasswordGetter(IPasswordGetterDelegate delegate) {
this.delegate = delegate;
}
public void setPassword(SecureCharSequence password) {
this.password = password;
}
public boolean hasPassword() {
return password != null;
}
public SecureCharSequence getPassword() {
if (password == null) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (delegate != null) {
delegate.beforePasswordDialogShow();
}
PasswordPanel d = new PasswordPanel(PasswordGetter.this);
d.showPanel();
}
});
try {
getPasswordLock.lockInterruptibly();
withPasswordCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
getPasswordLock.unlock();
}
}
return password;
}
@Override
public void onPasswordEntered(SecureCharSequence password) {
setPassword(password);
try {
getPasswordLock.lock();
withPasswordCondition.signal();
} finally {
getPasswordLock.unlock();
}
if (delegate != null && password != null) {
delegate.afterPasswordDialogDismiss();
}
}
public void wipe() {
if (password != null) {
password.wipe();
password = null;
}
}
}
}