Support resetting user's master password.
This commit is contained in:
parent
978b758079
commit
37a7cfa530
@ -82,11 +82,15 @@ public class MPMasterKey {
|
|||||||
Arrays.fill( masterPassword, (char) 0 );
|
Arrays.fill( masterPassword, (char) 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isValid() {
|
||||||
|
return !invalidated;
|
||||||
|
}
|
||||||
|
|
||||||
private byte[] masterKey(final MPAlgorithm algorithm)
|
private byte[] masterKey(final MPAlgorithm algorithm)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
Preconditions.checkArgument( masterPassword.length > 0 );
|
Preconditions.checkArgument( masterPassword.length > 0 );
|
||||||
|
|
||||||
if (invalidated)
|
if (!isValid())
|
||||||
throw new MPKeyUnavailableException( "Master key was invalidated." );
|
throw new MPKeyUnavailableException( "Master key was invalidated." );
|
||||||
|
|
||||||
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
byte[] masterKey = keyByVersion.get( algorithm.version() );
|
||||||
|
@ -21,6 +21,7 @@ package com.lyndir.masterpassword.gui.util;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -91,11 +92,21 @@ public abstract class Components {
|
|||||||
return new GradientPanel( layout, color );
|
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 );
|
JDialog dialog = pane.createDialog( owner, title );
|
||||||
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
|
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) {
|
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" );
|
return icon( "media/icon_lock.png" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Icon reset() {
|
||||||
|
return icon( "media/icon_reset.png" );
|
||||||
|
}
|
||||||
|
|
||||||
public Icon settings() {
|
public Icon settings() {
|
||||||
return icon( "media/icon_settings.png" );
|
return icon( "media/icon_settings.png" );
|
||||||
}
|
}
|
||||||
|
@ -142,6 +142,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
|
|
||||||
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(),
|
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(),
|
||||||
"Delete this user from Master Password." );
|
"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 JPasswordField masterPasswordField = Components.passwordField();
|
||||||
private final JLabel errorLabel = Components.label();
|
private final JLabel errorLabel = Components.label();
|
||||||
@ -156,6 +158,7 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
|
|
||||||
userToolbar.add( addButton );
|
userToolbar.add( addButton );
|
||||||
userToolbar.add( deleteButton );
|
userToolbar.add( deleteButton );
|
||||||
|
userToolbar.add( resetButton );
|
||||||
|
|
||||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||||
add( Components.strut() );
|
add( Components.strut() );
|
||||||
@ -180,12 +183,48 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
|
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
|
||||||
this, strf( "<html>Delete the user <strong>%s</strong>?<br><br><em>%s</em></html>",
|
SwingUtilities.windowForComponent( this ), strf( "<html>Delete the user <strong>%s</strong>?<br><br><em>%s</em></html>",
|
||||||
fileUser.getFullName(), fileUser.getFile().getName() ),
|
fileUser.getFullName(), fileUser.getFile().getName() ),
|
||||||
"Delete User", JOptionPane.YES_NO_OPTION ))
|
"Delete User", JOptionPane.YES_NO_OPTION ))
|
||||||
MPFileUserManager.get().delete( fileUser );
|
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
|
@Override
|
||||||
public void actionPerformed(final ActionEvent event) {
|
public void actionPerformed(final ActionEvent event) {
|
||||||
updateIdenticon();
|
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)
|
void authenticate(MPMasterKey masterKey)
|
||||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all authentication tokens and secrets from memory, effectively logging the user out.
|
||||||
|
*/
|
||||||
void invalidate();
|
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();
|
boolean isMasterKeyAvailable();
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -97,12 +97,14 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
@Override
|
@Override
|
||||||
public byte[] getKeyID() {
|
public byte[] getKeyID() {
|
||||||
try {
|
try {
|
||||||
|
if (isMasterKeyAvailable())
|
||||||
return getMasterKey().getKeyID( getAlgorithm() );
|
return getMasterKey().getKeyID( getAlgorithm() );
|
||||||
}
|
}
|
||||||
catch (final MPException e) {
|
catch (final MPException e) {
|
||||||
logger.wrn( e, "While deriving key ID for user: %s", this );
|
logger.wrn( e, "While deriving key ID for user: %s", this );
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -144,22 +146,28 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
if (masterKey == null)
|
if (masterKey == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.masterKey = null;
|
masterKey.invalidate();
|
||||||
|
masterKey = null;
|
||||||
|
|
||||||
for (final Listener listener : listeners)
|
for (final Listener listener : listeners)
|
||||||
listener.onUserInvalidated( this );
|
listener.onUserInvalidated( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isMasterKeyAvailable() {
|
public boolean isMasterKeyAvailable() {
|
||||||
return masterKey != null;
|
return (masterKey != null) && masterKey.isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPMasterKey getMasterKey()
|
public MPMasterKey getMasterKey()
|
||||||
throws MPKeyUnavailableException {
|
throws MPKeyUnavailableException {
|
||||||
if (masterKey == null)
|
if ((masterKey == null) || !masterKey.isValid())
|
||||||
throw new MPKeyUnavailableException( "Master key was not yet set for: " + this );
|
throw new MPKeyUnavailableException( "Master key was not yet set for: " + this );
|
||||||
|
|
||||||
return masterKey;
|
return masterKey;
|
||||||
|
@ -169,6 +169,13 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void reset() {
|
||||||
|
keyID = null;
|
||||||
|
|
||||||
|
super.reset();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MPFileSite addSite(final String siteName) {
|
public MPFileSite addSite(final String siteName) {
|
||||||
return addSite( new MPFileSite( this, siteName ) );
|
return addSite( new MPFileSite( this, siteName ) );
|
||||||
|
Loading…
Reference in New Issue
Block a user