Support resetting user's master password.
This commit is contained in:
		@@ -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() );
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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" );
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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( "<html>Delete the user <strong>%s</strong>?<br><br><em>%s</em></html>",
 | 
			
		||||
                                fileUser.getFullName(), fileUser.getFile().getName() ),
 | 
			
		||||
                    SwingUtilities.windowForComponent( this ), strf( "<html>Delete the user <strong>%s</strong>?<br><br><em>%s</em></html>",
 | 
			
		||||
                                                                     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( "<html>Enter the new master password for <strong>%s</strong>:</html>",
 | 
			
		||||
                                            user.getFullName() ) ),
 | 
			
		||||
                    Components.strut(),
 | 
			
		||||
                    passwordField,
 | 
			
		||||
                    Components.strut(),
 | 
			
		||||
                    Components.label( strf( "<html><em><strong>Note:</strong><br>Changing the master password "
 | 
			
		||||
                                            + "will change all of the user's passwords.<br>"
 | 
			
		||||
                                            + "Changing back to the original master password will also restore<br>"
 | 
			
		||||
                                            + "the user's original passwords.</em></html>",
 | 
			
		||||
                                            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();
 | 
			
		||||
 
 | 
			
		||||
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 2.0 KiB  | 
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.1 KiB  | 
@@ -77,8 +77,18 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
 | 
			
		||||
    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
 | 
			
		||||
 
 | 
			
		||||
@@ -97,12 +97,14 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> 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<S extends MPBasicSite<?, ?>> 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;
 | 
			
		||||
 
 | 
			
		||||
@@ -169,6 +169,13 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void reset() {
 | 
			
		||||
        keyID = null;
 | 
			
		||||
 | 
			
		||||
        super.reset();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public MPFileSite addSite(final String siteName) {
 | 
			
		||||
        return addSite( new MPFileSite( this, siteName ) );
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user