2
0

Added support for the C CLI's ~/.mpw config file to speed up sign-in to the GUI.

This commit is contained in:
Maarten Billemont 2014-06-11 01:45:31 -04:00
parent cced75cdfe
commit 6808016ab7
8 changed files with 390 additions and 137 deletions

View File

@ -0,0 +1,32 @@
package com.lyndir.lhunath.masterpassword;
import com.apple.eawt.*;
/**
* @author lhunath, 2014-06-10
*/
public class AppleGUI extends GUI {
public AppleGUI() {
Application application = Application.getApplication();
application.addAppEventListener( new AppForegroundListener() {
@Override
public void appMovedToBackground(AppEvent.AppForegroundEvent arg0) {
}
@Override
public void appRaisedToForeground(AppEvent.AppForegroundEvent arg0) {
open();
}
} );
application.addAppEventListener( new AppReOpenedListener() {
@Override
public void appReOpened(AppEvent.AppReOpenedEvent arg0) {
open();
}
} );
}
}

View File

@ -0,0 +1,40 @@
package com.lyndir.lhunath.masterpassword;
import com.google.common.io.Resources;
import java.awt.*;
import javax.swing.*;
/**
* @author lhunath, 2014-06-11
*/
public abstract class AuthenticationPanel extends JPanel {
protected final UnlockFrame unlockFrame;
public AuthenticationPanel(final UnlockFrame unlockFrame) {
this.unlockFrame = unlockFrame;
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
// Avatar
add( Box.createVerticalGlue() );
add( new JLabel( new ImageIcon( Resources.getResource( "media/Avatars/avatar-0.png" ) ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
}
} );
add( Box.createVerticalGlue() );
}
protected void updateUser() {
unlockFrame.setUser( getUser() );
}
protected abstract User getUser();
public Component getFocusComponent() {
return null;
}
}

View File

@ -0,0 +1,87 @@
package com.lyndir.lhunath.masterpassword;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.io.CharStreams;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.util.Iterator;
import javax.swing.*;
/**
* @author lhunath, 2014-06-11
*/
public class ConfigAuthenticationPanel extends AuthenticationPanel implements ItemListener, ActionListener {
private final JComboBox userField;
public ConfigAuthenticationPanel(final UnlockFrame unlockFrame) {
// User
super( unlockFrame );
JLabel userLabel = new JLabel( "User:" );
userLabel.setAlignmentX( LEFT_ALIGNMENT );
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( userLabel );
userField = new JComboBox<User>( new DefaultComboBoxModel<>( readConfigUsers() ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
userField.setAlignmentX( LEFT_ALIGNMENT );
userField.addItemListener( this );
userField.addActionListener( this );
add( userField );
}
@Override
protected User getUser() {
return (User) userField.getSelectedItem();
}
public static boolean hasConfigUsers() {
return new File( System.getProperty( "user.home" ), ".mpw" ).canRead();
}
private User[] readConfigUsers() {
ImmutableList.Builder<User> users = ImmutableList.builder();
File mpwConfig = new File( System.getProperty( "user.home" ), ".mpw" );
try (FileReader mpwReader = new FileReader( mpwConfig )) {
for (String line : CharStreams.readLines( mpwReader )) {
if (line.startsWith( "#" ) || line.startsWith( "//" ) || line.isEmpty()) {
continue;
}
Iterator<String> fields = Splitter.on( ':' ).limit( 2 ).split( line ).iterator();
String userName = fields.next(), masterPassword = fields.next();
users.add( new User( userName, masterPassword ) );
}
return Iterables.toArray( users.build(), User.class );
}
catch (FileNotFoundException e) {
return null;
}
catch (IOException e) {
e.printStackTrace();
return null;
}
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser();
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser();
unlockFrame.trySignIn( userField );
}
}

View File

@ -15,7 +15,8 @@
*/ */
package com.lyndir.lhunath.masterpassword; package com.lyndir.lhunath.masterpassword;
import com.apple.eawt.*; import com.google.common.base.Optional;
import com.lyndir.lhunath.opal.system.util.TypeUtils;
import java.io.IOException; import java.io.IOException;
import javax.swing.*; import javax.swing.*;
@ -33,37 +34,18 @@ public class GUI implements UnlockFrame.SignInCallback {
public static void main(final String[] args) public static void main(final String[] args)
throws IOException { throws IOException {
// Apple
Optional<? extends GUI> appleGUI = TypeUtils.newInstance( AppleGUI.class );
if (appleGUI.isPresent()) {
appleGUI.get().open();
return;
}
// All others
new GUI().open(); new GUI().open();
} }
public GUI() { void open() {
try {
getClass().getClassLoader().loadClass( "com.apple.eawt.Application" );
Application application = Application.getApplication();
application.addAppEventListener( new AppForegroundListener() {
@Override
public void appMovedToBackground(AppEvent.AppForegroundEvent arg0) {
}
@Override
public void appRaisedToForeground(AppEvent.AppForegroundEvent arg0) {
open();
}
} );
application.addAppEventListener( new AppReOpenedListener() {
@Override
public void appReOpened(AppEvent.AppReOpenedEvent arg0) {
open();
}
} );
}
catch (ClassNotFoundException | NoClassDefFoundError ignored) {
}
}
private void open() {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@ -77,9 +59,13 @@ public class GUI implements UnlockFrame.SignInCallback {
} }
@Override @Override
public boolean signedIn(final String userName, final String masterPassword) { public boolean signedIn(final User user) {
final byte[] key = MasterPassword.keyForPassword( masterPassword, userName ); if (!user.hasKey()) {
passwordFrame = new PasswordFrame( new User( userName, key ) ); return false;
}
user.getKey();
passwordFrame = new PasswordFrame( user );
open(); open();
return true; return true;

View File

@ -40,7 +40,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
// User // User
add( new JLabel( strf( "Generating passwords for: %s", user.getName() ) ), BorderLayout.NORTH ); add( label = new JLabel( strf( "Generating passwords for: %s", user.getName() ) ), BorderLayout.NORTH );
label.setAlignmentX( LEFT_ALIGNMENT );
// Site // Site
JPanel sitePanel = new JPanel(); JPanel sitePanel = new JPanel();
@ -49,13 +50,16 @@ public class PasswordFrame extends JFrame implements DocumentListener {
add( sitePanel, BorderLayout.CENTER ); add( sitePanel, BorderLayout.CENTER );
// Site Name // Site Name
sitePanel.add( new JLabel( "Site Name:", JLabel.LEADING ) ); sitePanel.add( label = new JLabel( "Site Name:", JLabel.LEADING ) );
label.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteNameField = new JTextField() { sitePanel.add( siteNameField = new JTextField() {
@Override @Override
public Dimension getMaximumSize() { public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
} ); } );
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
siteNameField.getDocument().addDocumentListener( this ); siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() { siteNameField.addActionListener( new ActionListener() {
@Override @Override
@ -86,7 +90,9 @@ public class PasswordFrame extends JFrame implements DocumentListener {
return new Dimension( 50, getPreferredSize().height ); return new Dimension( 50, getPreferredSize().height );
} }
} ) ); } ) );
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField.setSelectedItem( MPElementType.GeneratedLong ); siteTypeField.setSelectedItem( MPElementType.GeneratedLong );
siteCounterField.setAlignmentX( LEFT_ALIGNMENT );
siteCounterField.addChangeListener( new ChangeListener() { siteCounterField.addChangeListener( new ChangeListener() {
@Override @Override
public void stateChanged(final ChangeEvent e) { public void stateChanged(final ChangeEvent e) {
@ -96,6 +102,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
// Password // Password
add( passwordLabel = new JLabel( " ", JLabel.CENTER ), BorderLayout.SOUTH ); add( passwordLabel = new JLabel( " ", JLabel.CENTER ), BorderLayout.SOUTH );
passwordLabel.setAlignmentX( LEFT_ALIGNMENT );
passwordLabel.setFont( passwordLabel.getFont().deriveFont( 40f ) ); passwordLabel.setFont( passwordLabel.getFont().deriveFont( 40f ) );
pack(); pack();

View File

@ -0,0 +1,88 @@
package com.lyndir.lhunath.masterpassword;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
public class TextAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener {
private final JTextField userNameField;
private final JPasswordField masterPasswordField;
public TextAuthenticationPanel(final UnlockFrame unlockFrame) {
// User Name
super( unlockFrame );
JLabel userNameLabel = new JLabel( "User Name:" );
userNameLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
userNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
userNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( userNameLabel );
userNameField = new JTextField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
userNameField.setAlignmentX( Component.LEFT_ALIGNMENT );
userNameField.getDocument().addDocumentListener( this );
userNameField.addActionListener( this );
add( userNameField );
// Master Password
JLabel masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel );
masterPasswordField = new JPasswordField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return userNameField;
}
@Override
protected User getUser() {
return new User( userNameField.getText(), new String( masterPasswordField.getPassword() ) );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser();
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser();
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser();
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser();
unlockFrame.trySignIn(userNameField,masterPasswordField );
}
}

View File

@ -1,96 +1,48 @@
package com.lyndir.lhunath.masterpassword; package com.lyndir.lhunath.masterpassword;
import com.google.common.io.Resources; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import com.lyndir.lhunath.masterpassword.util.Components; import com.lyndir.lhunath.masterpassword.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.*;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.*; import javax.swing.border.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/** /**
* @author lhunath, 2014-06-08 * @author lhunath, 2014-06-08
*/ */
public class UnlockFrame extends JFrame implements DocumentListener { public class UnlockFrame extends JFrame {
private static final ExecutorService executor = Executors.newSingleThreadExecutor(); private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private final SignInCallback signInCallback; private final SignInCallback signInCallback;
private final JPanel root; private final JPanel root;
private final JLabel avatarView; private final JButton signInButton;
private final JTextField userNameField; private final JPanel authenticationContainer;
private final JTextField masterPasswordField; private boolean useConfig;
private final JButton signInButton; public User user;
public UnlockFrame(final SignInCallback signInCallback) public UnlockFrame(final SignInCallback signInCallback)
throws HeadlessException { throws HeadlessException {
super( "Unlock Master Password" ); super( "Unlock Master Password" );
this.signInCallback = signInCallback; this.signInCallback = signInCallback;
JLabel label;
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) ); setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) );
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) ); root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
JPanel userAndPassword = new JPanel(); authenticationContainer = new JPanel();
userAndPassword.setLayout( new BoxLayout( userAndPassword, BoxLayout.PAGE_AXIS ) ); authenticationContainer.setLayout( new BoxLayout( authenticationContainer, BoxLayout.PAGE_AXIS ) );
userAndPassword.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) ); authenticationContainer.setBorder( new CompoundBorder( new EtchedBorder( EtchedBorder.RAISED ), new EmptyBorder( 8, 8, 8, 8 ) ) );
add( userAndPassword, BorderLayout.CENTER ); add( authenticationContainer );
// Avatar
userAndPassword.add( Box.createVerticalGlue() );
userAndPassword.add( avatarView = new JLabel( new ImageIcon( Resources.getResource( "media/Avatars/avatar-0.png" ) ) ) {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
}
} );
userAndPassword.add( Box.createVerticalGlue() );
// User Name
userAndPassword.add( label = new JLabel( "User Name:" ) );
label.setHorizontalAlignment( SwingConstants.CENTER );
label.setVerticalAlignment( SwingConstants.BOTTOM );
userAndPassword.add( userNameField = new JTextField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
} );
userNameField.getDocument().addDocumentListener( this );
userNameField.addActionListener( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
trySignIn();
}
} );
// Master Password
userAndPassword.add( label = new JLabel( "Master Password:" ) );
label.setHorizontalAlignment( SwingConstants.CENTER );
label.setVerticalAlignment( SwingConstants.BOTTOM );
userAndPassword.add( masterPasswordField = new JPasswordField() {
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
} );
masterPasswordField.getDocument().addDocumentListener( this );
masterPasswordField.addActionListener( new AbstractAction() {
@Override
public void actionPerformed(final ActionEvent e) {
trySignIn();
}
} );
// Sign In // Sign In
add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ), root.add( Components.boxLayout( BoxLayout.LINE_AXIS, Box.createGlue(), signInButton = new JButton( "Sign In" ), Box.createGlue() ),
BorderLayout.SOUTH ); BorderLayout.SOUTH );
signInButton.setAlignmentX( LEFT_ALIGNMENT );
signInButton.addActionListener( new AbstractAction() { signInButton.addActionListener( new AbstractAction() {
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
@ -98,43 +50,91 @@ public class UnlockFrame extends JFrame implements DocumentListener {
} }
} ); } );
checkSignIn(); useConfig = ConfigAuthenticationPanel.hasConfigUsers();
createAuthenticationPanel();
pack();
setMinimumSize( getSize() );
setPreferredSize( new Dimension( 300, 300 ) );
pack();
setLocationByPlatform( true ); setLocationByPlatform( true );
setLocationRelativeTo( null ); setLocationRelativeTo( null );
} }
private boolean checkSignIn() { private void repack() {
String userName = userNameField.getText(); setPreferredSize( null );
String masterPassword = masterPasswordField.getText(); pack();
setMinimumSize( getSize() );
setPreferredSize( new Dimension( 300, 300 ) );
pack();
}
boolean enabled = !userName.isEmpty() && !masterPassword.isEmpty(); private void createAuthenticationPanel() {
authenticationContainer.removeAll();
final AuthenticationPanel authenticationPanel;
if (useConfig) {
authenticationPanel = new ConfigAuthenticationPanel( this );
} else {
authenticationPanel = new TextAuthenticationPanel( this );
}
authenticationPanel.updateUser();
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
final JCheckBox configCheckBox = new JCheckBox( "Use Config File" );
configCheckBox.setAlignmentX( LEFT_ALIGNMENT );
configCheckBox.setSelected( useConfig );
configCheckBox.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
useConfig = configCheckBox.isSelected();
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
createAuthenticationPanel();
}
} );
}
} );
authenticationContainer.add( configCheckBox );
checkSignIn();
validate();
repack();
SwingUtilities.invokeLater( new Runnable() {
@Override
public void run() {
ifNotNullElse( authenticationPanel.getFocusComponent(), signInButton ).requestFocusInWindow();
}
} );
}
void setUser(User user) {
this.user = user;
checkSignIn();
}
boolean checkSignIn() {
boolean enabled = user != null && !user.getName().isEmpty() && user.hasKey();
signInButton.setEnabled( enabled ); signInButton.setEnabled( enabled );
return enabled; return enabled;
} }
private void trySignIn() { void trySignIn(final JComponent... signInComponents) {
if (!checkSignIn()) if (!checkSignIn()) {
return; return;
}
final String userName = userNameField.getText(); for (JComponent signInComponent : signInComponents) {
final String masterPassword = masterPasswordField.getText(); signInComponent.setEnabled( false );
}
userNameField.setEnabled( false );
masterPasswordField.setEnabled( false );
signInButton.setEnabled( false ); signInButton.setEnabled( false );
signInButton.setText( "Signing In..." ); signInButton.setText( "Signing In..." );
executor.submit( new Runnable() { executor.submit( new Runnable() {
@Override @Override
public void run() { public void run() {
final boolean success = signInCallback.signedIn( userName, masterPassword ); final boolean success = signInCallback.signedIn( user );
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@ -143,9 +143,10 @@ public class UnlockFrame extends JFrame implements DocumentListener {
return; return;
} }
userNameField.setEnabled( true );
masterPasswordField.setEnabled( true );
signInButton.setText( "Sign In" ); signInButton.setText( "Sign In" );
for (JComponent signInComponent : signInComponents) {
signInComponent.setEnabled( true );
}
checkSignIn(); checkSignIn();
} }
} ); } );
@ -153,23 +154,8 @@ public class UnlockFrame extends JFrame implements DocumentListener {
} ); } );
} }
@Override
public void insertUpdate(final DocumentEvent e) {
checkSignIn();
}
@Override
public void removeUpdate(final DocumentEvent e) {
checkSignIn();
}
@Override
public void changedUpdate(final DocumentEvent e) {
checkSignIn();
}
interface SignInCallback { interface SignInCallback {
boolean signedIn(String userName, String masterPassword); boolean signedIn(User user);
} }
} }

View File

@ -1,22 +1,49 @@
package com.lyndir.lhunath.masterpassword; package com.lyndir.lhunath.masterpassword;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
/** /**
* @author lhunath, 2014-06-08 * @author lhunath, 2014-06-08
*/ */
public class User { public class User {
private final String name;
private final byte[] key;
public User(final String name, final byte[] key) { private final String name;
private final String masterPassword;
private byte[] key;
public User(final String name, final String masterPassword) {
this.name = name; this.name = name;
this.key = key; this.masterPassword = masterPassword;
} }
public String getName() { public String getName() {
return name; return name;
} }
public boolean hasKey() {
return key != null || (masterPassword != null && !masterPassword.isEmpty());
}
public byte[] getKey() { public byte[] getKey() {
if (key == null) {
if (!hasKey()) {
throw new IllegalStateException( strf( "Master password unknown for user: %s", name ) );
} else {
key = MasterPassword.keyForPassword( masterPassword, name );
}
}
return key; return key;
} }
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public String toString() {
return name;
}
} }