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 ) );