diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java
index 0ef0e20b..15c6d458 100644
--- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java
+++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java
@@ -82,11 +82,15 @@ public class MPMasterKey {
Arrays.fill( masterPassword, (char) 0 );
}
+ public boolean isValid() {
+ return !invalidated;
+ }
+
private byte[] masterKey(final MPAlgorithm algorithm)
throws MPKeyUnavailableException, MPAlgorithmException {
Preconditions.checkArgument( masterPassword.length > 0 );
- if (invalidated)
+ if (!isValid())
throw new MPKeyUnavailableException( "Master key was invalidated." );
byte[] masterKey = keyByVersion.get( algorithm.version() );
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
index 8bc68a9f..a3d7ba04 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java
@@ -21,6 +21,7 @@ package com.lyndir.masterpassword.gui.util;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
+import java.util.Arrays;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -91,11 +92,21 @@ public abstract class Components {
return new GradientPanel( layout, color );
}
- public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
+ public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
JDialog dialog = pane.createDialog( owner, title );
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
+ showDialog( dialog );
- return showDialog( dialog );
+ Object selectedValue = pane.getValue();
+ if(selectedValue == null)
+ return JOptionPane.CLOSED_OPTION;
+
+ Object[] options = pane.getOptions();
+ if(options == null)
+ return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
+
+ int option = Arrays.binarySearch( options, selectedValue );
+ return (option < 0)? JOptionPane.CLOSED_OPTION: option;
}
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) {
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java
index cffcc92f..e4bba494 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java
@@ -132,6 +132,10 @@ public abstract class Res {
return icon( "media/icon_lock.png" );
}
+ public Icon reset() {
+ return icon( "media/icon_reset.png" );
+ }
+
public Icon settings() {
return icon( "media/icon_settings.png" );
}
diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java
index 795bb580..65c14784 100644
--- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java
+++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java
@@ -37,8 +37,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
private static final Logger logger = Logger.get( UserContentPanel.class );
private static final JButton iconButton = Components.button( Res.icons().user(), null, null );
- private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
- "Add a new user to Master Password." );
+ private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
+ "Add a new user to Master Password." );
private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
@@ -142,6 +142,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(),
"Delete this user from Master Password." );
+ private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
+ "Change the master password for this user." );
private final JPasswordField masterPasswordField = Components.passwordField();
private final JLabel errorLabel = Components.label();
@@ -156,6 +158,7 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
userToolbar.add( addButton );
userToolbar.add( deleteButton );
+ userToolbar.add( resetButton );
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
add( Components.strut() );
@@ -180,12 +183,48 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
return;
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
- this, strf( "Delete the user %s?
%s",
- fileUser.getFullName(), fileUser.getFile().getName() ),
+ SwingUtilities.windowForComponent( this ), strf( "Delete the user %s?
%s",
+ fileUser.getFullName(), fileUser.getFile().getName() ),
"Delete User", JOptionPane.YES_NO_OPTION ))
MPFileUserManager.get().delete( fileUser );
}
+ private void resetUser() {
+ JPasswordField passwordField = Components.passwordField();
+ if (JOptionPane.OK_OPTION == Components.showDialog( this, "Reset User", new JOptionPane( Components.panel(
+ BoxLayout.PAGE_AXIS,
+ Components.label( strf( "Enter the new master password for %s:",
+ user.getFullName() ) ),
+ Components.strut(),
+ passwordField,
+ Components.strut(),
+ Components.label( strf( "Note:
Changing the master password "
+ + "will change all of the user's passwords.
"
+ + "Changing back to the original master password will also restore
"
+ + "the user's original passwords.",
+ user.getFullName() ) ) ), JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) {
+ @Override
+ public void selectInitialValue() {
+ passwordField.requestFocusInWindow();
+ }
+ } )) {
+ char[] masterPassword = passwordField.getPassword();
+ if ((masterPassword != null) && (masterPassword.length > 0))
+ try {
+ user.reset();
+ user.authenticate( masterPassword );
+ }
+ catch (final MPIncorrectMasterPasswordException e) {
+ errorLabel.setText( e.getLocalizedMessage() );
+ throw logger.bug( e );
+ }
+ catch (final MPAlgorithmException e) {
+ logger.err( e, "While resetting master password." );
+ errorLabel.setText( e.getLocalizedMessage() );
+ }
+ }
+ }
+
@Override
public void actionPerformed(final ActionEvent event) {
updateIdenticon();
diff --git a/platform-independent/java/gui/src/main/resources/media/icon_reset.png b/platform-independent/java/gui/src/main/resources/media/icon_reset.png
new file mode 100644
index 00000000..754d5863
Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_reset.png differ
diff --git a/platform-independent/java/gui/src/main/resources/media/icon_reset@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_reset@2x.png
new file mode 100644
index 00000000..be4eb451
Binary files /dev/null and b/platform-independent/java/gui/src/main/resources/media/icon_reset@2x.png differ
diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
index 7d352cca..e9be467c 100644
--- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
+++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/MPUser.java
@@ -77,8 +77,18 @@ public interface MPUser> extends Comparable> {
void authenticate(MPMasterKey masterKey)
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
+ /**
+ * Clear all authentication tokens and secrets from memory, effectively logging the user out.
+ */
void invalidate();
+ /**
+ * Wipe the key ID, allowing the user to {@link #authenticate(char[])} with any master password.
+ *
+ * Note: Authenticating with a different master password will cause all of the user's results to change.
+ */
+ void reset();
+
boolean isMasterKeyAvailable();
@Nonnull
diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java
index 2c73f915..eef907c2 100755
--- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java
+++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java
@@ -97,12 +97,14 @@ public abstract class MPBasicUser> extends Changeabl
@Override
public byte[] getKeyID() {
try {
- return getMasterKey().getKeyID( getAlgorithm() );
+ if (isMasterKeyAvailable())
+ return getMasterKey().getKeyID( getAlgorithm() );
}
catch (final MPException e) {
logger.wrn( e, "While deriving key ID for user: %s", this );
- return null;
}
+
+ return null;
}
@Nullable
@@ -143,23 +145,29 @@ public abstract class MPBasicUser> extends Changeabl
public void invalidate() {
if (masterKey == null)
return;
-
- this.masterKey = null;
+
+ masterKey.invalidate();
+ masterKey = null;
for (final Listener listener : listeners)
listener.onUserInvalidated( this );
}
+ @Override
+ public void reset() {
+ invalidate();
+ }
+
@Override
public boolean isMasterKeyAvailable() {
- return masterKey != null;
+ return (masterKey != null) && masterKey.isValid();
}
@Nonnull
@Override
public MPMasterKey getMasterKey()
throws MPKeyUnavailableException {
- if (masterKey == null)
+ if ((masterKey == null) || !masterKey.isValid())
throw new MPKeyUnavailableException( "Master key was not yet set for: " + this );
return masterKey;
diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java
index a1c4b181..acbdf2bc 100755
--- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java
+++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java
@@ -169,6 +169,13 @@ public class MPFileUser extends MPBasicUser {
}
}
+ @Override
+ public void reset() {
+ keyID = null;
+
+ super.reset();
+ }
+
@Override
public MPFileSite addSite(final String siteName) {
return addSite( new MPFileSite( this, siteName ) );