2
0

WIP - integrate user and site storage through export files into Java Swing GUI.

This commit is contained in:
Maarten Billemont 2014-12-11 20:35:19 -05:00
parent 4ff8cd6d90
commit 84b624aea2
25 changed files with 512 additions and 382 deletions

View File

@ -181,10 +181,14 @@ public enum MPSiteType {
*/ */
public static ImmutableList<MPSiteType> forMask(final int mask) { public static ImmutableList<MPSiteType> forMask(final int mask) {
int typeIndex = mask & 0xF, typeMask = mask & ~0xF;
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder(); ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
for (MPSiteType siteType : values()) for (MPSiteType siteType : values()) {
if ((siteType.getMask() & mask) != 0) int siteMask = siteType.getMask(), siteTypeIndex = siteMask & 0xF, siteTypeMask = siteMask & ~0xF;
if ((siteTypeMask & typeMask) != 0 && (typeIndex == 0 || siteTypeIndex == typeIndex))
types.add( siteType ); types.add( siteType );
}
return types.build(); return types.build();
} }

View File

@ -52,13 +52,14 @@ public class MasterKey {
String mpKeyScope = MPSiteVariant.Password.getScope(); String mpKeyScope = MPSiteVariant.Password.getScope();
byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes ); byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes );
logger.trc( "key scope: %s", mpKeyScope ); logger.trc( "key scope: %s", mpKeyScope );
logger.trc( "masterKeySalt ID: %s", idForBytes( masterKeySalt ) ); logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
try { try {
masterKey = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen ); masterKey = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen );
valid = true; valid = true;
logger.trc( "masterKey ID: %s (derived in %.2fs)", idForBytes( masterKey ), (System.currentTimeMillis() - start) / 1000D ); logger.trc( "masterKey ID: %s (derived in %.2fs)", CodeUtils.encodeHex( idForBytes( masterKey ) ),
(System.currentTimeMillis() - start) / 1000D );
} }
catch (GeneralSecurityException e) { catch (GeneralSecurityException e) {
throw logger.bug( e ); throw logger.bug( e );
@ -70,7 +71,7 @@ public class MasterKey {
return fullName; return fullName;
} }
public String getKeyID() { public byte[] getKeyID() {
Preconditions.checkState( valid ); Preconditions.checkState( valid );
return idForBytes( masterKey ); return idForBytes( masterKey );
@ -111,10 +112,10 @@ public class MasterKey {
siteContext == null? "(null)": siteContext ); siteContext == null? "(null)": siteContext );
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes ); byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
logger.trc( "sitePasswordInfo ID: %s", idForBytes( sitePasswordInfo ) ); logger.trc( "sitePasswordInfo ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
byte[] sitePasswordSeed = MP_mac.of( masterKey, sitePasswordInfo ); byte[] sitePasswordSeed = MP_mac.of( masterKey, sitePasswordInfo );
logger.trc( "sitePasswordSeed ID: %s", idForBytes( sitePasswordSeed ) ); logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) );
Preconditions.checkState( sitePasswordSeed.length > 0 ); Preconditions.checkState( sitePasswordSeed.length > 0 );
int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign. int templateIndex = sitePasswordSeed[0] & 0xFF; // Mask the integer's sign.
@ -145,7 +146,7 @@ public class MasterKey {
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array(); return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
} }
private static String idForBytes(final byte[] bytes) { private static byte[] idForBytes(final byte[] bytes) {
return CodeUtils.encodeHex( MP_hash.of( bytes ) ); return MP_hash.of( bytes );
} }
} }

View File

@ -47,7 +47,7 @@
<configuration> <configuration>
<transformers> <transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>com.lyndir.masterpassword.GUI</mainClass> <mainClass>com.lyndir.masterpassword.gui.GUI</mainClass>
</transformer> </transformer>
</transformers> </transformers>
<filters> <filters>

View File

@ -1,164 +0,0 @@
package com.lyndir.masterpassword;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
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 java.util.NoSuchElementException;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
public class ConfigAuthenticationPanel extends AuthenticationPanel implements ItemListener, ActionListener, DocumentListener {
private final JComboBox userField;
private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField;
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 );
// Master Password
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 masterPasswordField.isVisible()? masterPasswordField: null;
}
@Override
protected void updateUser(boolean repack) {
boolean masterPasswordMissing = userField.getSelectedItem() == null || !((User) userField.getSelectedItem()).hasKey();
if (masterPasswordField.isVisible() != masterPasswordMissing) {
masterPasswordLabel.setVisible( masterPasswordMissing );
masterPasswordField.setVisible( masterPasswordMissing );
repack = true;
}
super.updateUser( repack );
}
@Override
protected User getUser() {
User selectedUser = (User) userField.getSelectedItem();
if (selectedUser.hasKey()) {
return selectedUser;
}
return new User( selectedUser.getUserName(), new String( masterPasswordField.getPassword() ) );
}
public String getHelpText() {
return "Reads users from ~/.mpw, the following syntax applies:\nUser Name:masterpassword"
+ "\n\nEnsure the file's permissions make it only readable by you!";
}
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) {
JOptionPane.showMessageDialog( this, "First create the config file at:\n" + mpwConfig.getAbsolutePath() +
"\n\nIt should contain a line for each user of the following format:" +
"\nUser Name:masterpassword" +
"\n\nEnsure the file's permissions make it only readable by you!", //
"Config File Not Found", JOptionPane.WARNING_MESSAGE );
return new User[0];
}
catch (IOException | NoSuchElementException e) {
e.printStackTrace();
String error = ifNotNullElse( e.getLocalizedMessage(), ifNotNullElse( e.getMessage(), e.toString() ) );
JOptionPane.showMessageDialog( this, //
"Problem reading config file:\n" + mpwConfig.getAbsolutePath() //
+ "\n\n" + error, //
"Config File Not Readable", JOptionPane.WARNING_MESSAGE );
return new User[0];
}
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( userField );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
}

View File

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

View File

@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.apple.eawt.*; import com.apple.eawt.*;
import javax.swing.*; import javax.swing.*;

View File

@ -1,5 +1,6 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
@ -41,7 +42,7 @@ public abstract class AuthenticationPanel extends JPanel {
return null; return null;
} }
public String getHelpText() { public Iterable<? extends JButton> getButtons() {
return null; return ImmutableList.of();
} }
} }

View File

@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;

View File

@ -15,19 +15,16 @@
*/ */
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.io.*; import com.google.common.io.*;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.MessageDigests;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils; import com.lyndir.lhunath.opal.system.util.TypeUtils;
import java.io.*; import java.io.*;
import java.net.URI; import java.net.URI;
import java.net.URL; import java.net.URL;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List;
import java.util.jar.*; import java.util.jar.*;
import javax.swing.*; import javax.swing.*;
@ -94,20 +91,18 @@ public class GUI implements UnlockFrame.SignInCallback {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
if (passwordFrame == null) { if (passwordFrame == null)
unlockFrame.setVisible( true ); unlockFrame.setVisible( true );
} else { else
passwordFrame.setVisible( true ); passwordFrame.setVisible( true );
}
} }
} ); } );
} }
@Override @Override
public boolean signedIn(final User user) { public boolean signedIn(final User user) {
if (!user.hasKey()) { if (!user.hasKey())
return false; return false;
}
user.getKey(); user.getKey();
passwordFrame = newPasswordFrame( user ); passwordFrame = newPasswordFrame( user );

View File

@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@ -11,35 +11,35 @@ import javax.swing.event.DocumentListener;
/** /**
* @author lhunath, 2014-06-11 * @author lhunath, 2014-06-11
*/ */
public class TextAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener { public class IncognitoAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener {
private final JTextField userNameField; private final JTextField fullNameField;
private final JPasswordField masterPasswordField; private final JPasswordField masterPasswordField;
public TextAuthenticationPanel(final UnlockFrame unlockFrame) { public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) {
// User Name // Full Name
super( unlockFrame ); super( unlockFrame );
JLabel userNameLabel = new JLabel( "User Name:" ); JLabel fullNameLabel = new JLabel( "Full Name:" );
userNameLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
userNameLabel.setHorizontalAlignment( SwingConstants.CENTER ); fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
userNameLabel.setVerticalAlignment( SwingConstants.BOTTOM ); fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( userNameLabel ); add( fullNameLabel );
userNameField = new JTextField() { fullNameField = 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 );
} }
}; };
userNameField.setAlignmentX( Component.LEFT_ALIGNMENT ); fullNameField.setAlignmentX( LEFT_ALIGNMENT );
userNameField.getDocument().addDocumentListener( this ); fullNameField.getDocument().addDocumentListener( this );
userNameField.addActionListener( this ); fullNameField.addActionListener( this );
add( userNameField ); add( fullNameField );
// Master Password // Master Password
JLabel masterPasswordLabel = new JLabel( "Master Password:" ); JLabel masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER ); masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM ); masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel ); add( masterPasswordLabel );
@ -50,7 +50,7 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
}; };
masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT ); masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField ); add( masterPasswordField );
@ -58,12 +58,12 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
@Override @Override
public Component getFocusComponent() { public Component getFocusComponent() {
return userNameField; return fullNameField;
} }
@Override @Override
protected User getUser() { protected User getUser() {
return new User( userNameField.getText(), new String( masterPasswordField.getPassword() ) ); return new IncognitoUser( fullNameField.getText(), new String( masterPasswordField.getPassword() ) );
} }
@Override @Override
@ -84,6 +84,6 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
updateUser( false ); updateUser( false );
unlockFrame.trySignIn( userNameField, masterPasswordField ); unlockFrame.trySignIn( fullNameField, masterPasswordField );
} }
} }

View File

@ -0,0 +1,24 @@
package com.lyndir.masterpassword.gui;
/**
* @author lhunath, 2014-06-08
*/
public class IncognitoUser extends User {
private final String fullName;
private final String masterPassword;
public IncognitoUser(final String fullName, final String masterPassword) {
this.fullName = fullName;
this.masterPassword = masterPassword;
}
public String getFullName() {
return fullName;
}
@Override
protected String getMasterPassword() {
return masterPassword;
}
}

View File

@ -0,0 +1,163 @@
package com.lyndir.masterpassword.gui;
import com.google.common.base.Function;
import com.google.common.collect.*;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager;
import java.awt.*;
import java.awt.event.*;
import javax.annotation.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* @author lhunath, 2014-06-11
*/
public class ModelAuthenticationPanel extends AuthenticationPanel implements ItemListener, ActionListener, DocumentListener {
private final JComboBox<ModelUser> userField;
private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField;
public ModelAuthenticationPanel(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<ModelUser>( 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 );
// Master Password
masterPasswordLabel = new JLabel( "Master Password:" );
masterPasswordLabel.setAlignmentX( 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( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField );
}
@Override
public Component getFocusComponent() {
return masterPasswordField.isVisible()? masterPasswordField: null;
}
@Override
protected void updateUser(boolean repack) {
int selectedIndex = userField.getSelectedIndex();
if (selectedIndex >= 0) {
ModelUser selectedUser = userField.getModel().getElementAt( selectedIndex );
boolean showPasswordField = !selectedUser.keySaved();
if (masterPasswordField.isVisible() != showPasswordField) {
masterPasswordLabel.setVisible( showPasswordField );
masterPasswordField.setVisible( showPasswordField );
repack = true;
}
}
super.updateUser( repack );
}
@Override
protected User getUser() {
int selectedIndex = userField.getSelectedIndex();
if (selectedIndex < 0)
return null;
ModelUser selectedUser = userField.getModel().getElementAt( selectedIndex );
if (selectedUser != null)
selectedUser.setMasterPassword( new String( masterPasswordField.getPassword() ) );
return selectedUser;
}
@Override
public Iterable<? extends JButton> getButtons() {
return ImmutableList.of( new JButton( Res.iconAdd() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
"Enter your full name, ensuring it is correctly spelled and capitalized:",
"New User", JOptionPane.QUESTION_MESSAGE );
MPUserFileManager.get().addUser( new MPUser( fullName ) );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true );
}
} );
}
}, new JButton( Res.iconQuestion() ) {
{
addActionListener( new ActionListener() {
@Override
public void actionPerformed(final ActionEvent e) {
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
"Reads users and sites from the directory at ~/.mpw.", //
"Help", JOptionPane.INFORMATION_MESSAGE );
}
} );
}
} );
}
private ModelUser[] readConfigUsers() {
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
@Nullable
@Override
public ModelUser apply(final MPUser model) {
return new ModelUser( model );
}
} ).toArray( ModelUser.class );
}
@Override
public void itemStateChanged(final ItemEvent e) {
updateUser( false );
}
@Override
public void actionPerformed(final ActionEvent e) {
updateUser( false );
unlockFrame.trySignIn( userField );
}
@Override
public void insertUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void removeUpdate(final DocumentEvent e) {
updateUser( false );
}
@Override
public void changedUpdate(final DocumentEvent e) {
updateUser( false );
}
}

View File

@ -0,0 +1,52 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager;
/**
* @author lhunath, 14-12-08
*/
public class ModelUser extends User {
private final MPUser user;
private String masterPassword;
public ModelUser(MPUser user) {
this.user = user;
}
@Override
public String getFullName() {
return user.getFullName();
}
@Override
protected String getMasterPassword() {
return masterPassword;
}
public void setMasterPassword(final String masterPassword) {
this.masterPassword = masterPassword;
}
@Override
public MasterKey getKey() {
MasterKey key = super.getKey();
if (!user.hasKeyID()) {
user.setKeyID( key.getKeyID() );
MPUserFileManager.get().save();
}
else if (!user.hasKeyID( key.getKeyID() ))
throw new IllegalStateException( strf( "Incorrect master password for user: %s", getFullName() ) );
return key;
}
public boolean keySaved() {
return false;
}
}

View File

@ -1,8 +1,9 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.util.Components; import com.lyndir.masterpassword.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
@ -38,7 +39,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} ); } );
// User // User
add( label = new JLabel( strf( "Generating passwords for: %s", user.getUserName() ) ), BorderLayout.NORTH ); add( label = new JLabel( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
label.setFont( Res.exoRegular().deriveFont( 12f ) ); label.setFont( Res.exoRegular().deriveFont( 12f ) );
label.setAlignmentX( LEFT_ALIGNMENT ); label.setAlignmentX( LEFT_ALIGNMENT );

View File

@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
@ -45,6 +45,10 @@ public abstract class Res {
} ); } );
} }
public static Icon iconAdd() {
return new RetinaIcon( Resources.getResource( "media/icon_add@2x.png" ) );
}
public static Icon iconQuestion() { public static Icon iconQuestion() {
return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) ); return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) );
} }

View File

@ -1,4 +1,4 @@
package com.lyndir.masterpassword; package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
@ -18,7 +18,7 @@ public class UnlockFrame extends JFrame {
private final JPanel root; private final JPanel root;
private final JButton signInButton; private final JButton signInButton;
private final JPanel authenticationContainer; private final JPanel authenticationContainer;
private boolean useConfig; private boolean incognito;
public User user; public User user;
public UnlockFrame(final SignInCallback signInCallback) public UnlockFrame(final SignInCallback signInCallback)
@ -46,7 +46,6 @@ public class UnlockFrame extends JFrame {
} }
} ); } );
useConfig = ConfigAuthenticationPanel.hasConfigUsers();
createAuthenticationPanel(); createAuthenticationPanel();
setLocationByPlatform( true ); setLocationByPlatform( true );
@ -65,21 +64,21 @@ public class UnlockFrame extends JFrame {
authenticationContainer.removeAll(); authenticationContainer.removeAll();
final AuthenticationPanel authenticationPanel; final AuthenticationPanel authenticationPanel;
if (useConfig) { if (incognito) {
authenticationPanel = new ConfigAuthenticationPanel( this ); authenticationPanel = new IncognitoAuthenticationPanel( this );
} else { } else {
authenticationPanel = new TextAuthenticationPanel( this ); authenticationPanel = new ModelAuthenticationPanel( this );
} }
authenticationPanel.updateUser( false ); authenticationPanel.updateUser( false );
authenticationContainer.add( authenticationPanel, BorderLayout.CENTER ); authenticationContainer.add( authenticationPanel, BorderLayout.CENTER );
final JCheckBox typeCheckBox = new JCheckBox( "Use Config File" ); final JCheckBox incognitoCheckBox = new JCheckBox( "Incognito" );
typeCheckBox.setAlignmentX( LEFT_ALIGNMENT ); incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT );
typeCheckBox.setSelected( useConfig ); incognitoCheckBox.setSelected( incognito );
typeCheckBox.addItemListener( new ItemListener() { incognitoCheckBox.addItemListener( new ItemListener() {
@Override @Override
public void itemStateChanged(final ItemEvent e) { public void itemStateChanged(final ItemEvent e) {
useConfig = typeCheckBox.isSelected(); incognito = incognitoCheckBox.isSelected();
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@ -89,24 +88,15 @@ public class UnlockFrame extends JFrame {
} }
} ); } );
JButton typeHelp = new JButton( Res.iconQuestion() ); JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
typeHelp.setMargin( new Insets( 0, 0, 0, 0 ) ); toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
typeHelp.setBackground( Color.red ); authenticationContainer.add( toolsPanel );
typeHelp.setAlignmentX( RIGHT_ALIGNMENT ); for (JButton button : authenticationPanel.getButtons()) {
typeHelp.setBorder( null ); button.setMargin( new Insets( 0, 0, 0, 0 ) );
typeHelp.addActionListener( new ActionListener() { button.setAlignmentX( RIGHT_ALIGNMENT );
@Override button.setBorder( null );
public void actionPerformed(final ActionEvent e) { toolsPanel.add( button );
JOptionPane.showMessageDialog( UnlockFrame.this, authenticationPanel.getHelpText(), "Help",
JOptionPane.INFORMATION_MESSAGE );
}
} );
if (authenticationPanel.getHelpText() == null) {
typeHelp.setVisible( false );
} }
JComponent typePanel = Components.boxLayout( BoxLayout.LINE_AXIS, typeCheckBox, Box.createGlue(), typeHelp );
typePanel.setAlignmentX( Component.LEFT_ALIGNMENT );
authenticationContainer.add( typePanel );
checkSignIn(); checkSignIn();
validate(); validate();
@ -126,7 +116,7 @@ public class UnlockFrame extends JFrame {
} }
boolean checkSignIn() { boolean checkSignIn() {
boolean enabled = user != null && !user.getUserName().isEmpty() && user.hasKey(); boolean enabled = user != null && !user.getFullName().isEmpty() && user.hasKey();
signInButton.setEnabled( enabled ); signInButton.setEnabled( enabled );
return enabled; return enabled;

View File

@ -0,0 +1,45 @@
package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.lyndir.masterpassword.MasterKey;
import javax.annotation.Nonnull;
/**
* @author lhunath, 2014-06-08
*/
public abstract class User {
private MasterKey key;
public abstract String getFullName();
protected abstract String getMasterPassword();
public boolean hasKey() {
String masterPassword = getMasterPassword();
return key != null || (masterPassword != null && !masterPassword.isEmpty());
}
@Nonnull
public MasterKey getKey() {
if (key == null) {
if (!hasKey())
throw new IllegalStateException( strf( "Master password unknown for user: %s", getFullName() ) );
key = new MasterKey( getFullName(), getMasterPassword() );
}
return key;
}
@Override
public int hashCode() {
return getFullName().hashCode();
}
@Override
public String toString() {
return getFullName();
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@ -1,52 +0,0 @@
package com.lyndir.masterpassword.model;
import com.google.common.io.CharSink;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*;
/**
* @author lhunath, 14-12-07
*/
public class MPSiteFileManager extends MPSiteManager {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPSiteFileManager.class );
private final File file;
public static MPSiteFileManager create(final File file) {
try {
return new MPSiteFileManager( file );
}
catch (IOException e) {
throw logger.bug( e, "Unable to open sites from file: %s", file );
}
}
protected MPSiteFileManager(final File file)
throws IOException {
super( MPSiteUnmarshaller.unmarshall( file ).getUser() );
this.file = file;
}
public void save() {
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
return new FileWriter( file );
}
}.write( MPSiteMarshaller.marshallSafe( getUser() ).getExport() );
}
catch (IOException e) {
logger.err( e, "Unable to save sites to file: %s", file );
}
}
public File getFile() {
return file;
}
}

View File

@ -1,30 +0,0 @@
package com.lyndir.masterpassword.model;
import com.google.common.collect.ImmutableList;
import java.util.Collection;
/**
* @author lhunath, 14-12-05
*/
public abstract class MPSiteManager {
private final MPUser user;
public MPSiteManager(final MPUser user) {
this.user = user;
}
public MPUser getUser() {
return user;
}
public Collection<MPSiteResult> findSitesByName(String query) {
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
for (MPSite site : user.getSites())
if (site.getSiteName().startsWith( query ))
results.add( new MPSiteResult( site ) );
return results.build();
}
}

View File

@ -31,9 +31,9 @@ public class MPSiteUnmarshaller {
private static final Logger logger = Logger.get( MPSite.class ); private static final Logger logger = Logger.get( MPSite.class );
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTime(); private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTime();
private static final Pattern[] unmarshallFormats = new Pattern[]{ private static final Pattern[] unmarshallFormats = new Pattern[]{
Pattern.compile( "^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)" ), Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ),
Pattern.compile( "^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) }; Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
private static final Pattern headerFormat = Pattern.compile( "^#[[:space:]]*([^:]+): (.*)" ); private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" );
private final int importFormat; private final int importFormat;
private final int mpVersion; private final int mpVersion;

View File

@ -1,10 +1,11 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import java.util.*; import java.util.*;
import org.joda.time.DateTime; import org.joda.time.*;
/** /**
@ -12,18 +13,24 @@ import org.joda.time.DateTime;
*/ */
public class MPUser { public class MPUser {
private final String fullName; private final String fullName;
private final byte[] keyID;
private final int avatar;
private final MPSiteType defaultType;
private final DateTime lastUsed;
private final Collection<MPSite> sites = Sets.newHashSet(); private final Collection<MPSite> sites = Sets.newHashSet();
private byte[] keyID;
private int avatar;
private MPSiteType defaultType;
private ReadableInstant lastUsed;
public MPUser(final String fullName) {
this( fullName, null );
}
public MPUser(final String fullName, final byte[] keyID) { public MPUser(final String fullName, final byte[] keyID) {
this( fullName, keyID, 0, MPSiteType.GeneratedLong, new DateTime() ); this( fullName, keyID, 0, MPSiteType.GeneratedLong, new DateTime() );
} }
public MPUser(final String fullName, final byte[] keyID, final int avatar, final MPSiteType defaultType, final DateTime lastUsed) { public MPUser(final String fullName, final byte[] keyID, final int avatar, final MPSiteType defaultType,
final ReadableInstant lastUsed) {
this.fullName = fullName; this.fullName = fullName;
this.keyID = keyID; this.keyID = keyID;
this.avatar = avatar; this.avatar = avatar;
@ -31,6 +38,15 @@ public class MPUser {
this.lastUsed = lastUsed; this.lastUsed = lastUsed;
} }
public Collection<MPSiteResult> findSitesByName(String query) {
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
for (MPSite site : getSites())
if (site.getSiteName().startsWith( query ))
results.add( new MPSiteResult( site ) );
return results.build();
}
public void addSite(final MPSite site) { public void addSite(final MPSite site) {
sites.add( site ); sites.add( site );
} }
@ -39,6 +55,10 @@ public class MPUser {
return fullName; return fullName;
} }
public boolean hasKeyID() {
return keyID != null;
}
public boolean hasKeyID(final byte[] keyID) { public boolean hasKeyID(final byte[] keyID) {
return Arrays.equals( this.keyID, keyID ); return Arrays.equals( this.keyID, keyID );
} }
@ -47,18 +67,34 @@ public class MPUser {
return CodeUtils.encodeHex( keyID ); return CodeUtils.encodeHex( keyID );
} }
public void setKeyID(final byte[] keyID) {
this.keyID = keyID;
}
public int getAvatar() { public int getAvatar() {
return avatar; return avatar;
} }
public void setAvatar(final int avatar) {
this.avatar = avatar;
}
public MPSiteType getDefaultType() { public MPSiteType getDefaultType() {
return defaultType; return defaultType;
} }
public DateTime getLastUsed() { public void setDefaultType(final MPSiteType defaultType) {
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() {
return lastUsed; return lastUsed;
} }
public void updateLastUsed() {
this.lastUsed = new Instant();
}
public Iterable<MPSite> getSites() { public Iterable<MPSite> getSites() {
return sites; return sites;
} }

View File

@ -0,0 +1,77 @@
package com.lyndir.masterpassword.model;
import com.google.common.base.*;
import com.google.common.collect.*;
import com.google.common.io.CharSink;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.io.*;
import javax.annotation.Nullable;
/**
* @author lhunath, 14-12-07
*/
public class MPUserFileManager extends MPUserManager {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPUserFileManager.class );
private static final MPUserFileManager instance = create( new File( System.getProperty( "user.home" ), ".mpw" ) );
private final File userFilesDirectory;
public static MPUserFileManager get() {
return instance;
}
public static MPUserFileManager create(final File userFilesDirectory) {
return new MPUserFileManager( userFilesDirectory );
}
protected MPUserFileManager(final File userFilesDirectory) {
super( unmarshallUsers( userFilesDirectory ) );
this.userFilesDirectory = userFilesDirectory;
}
private static Iterable<MPUser> unmarshallUsers(final File userFilesDirectory) {
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
return ImmutableList.of();
}
return FluentIterable.from( ImmutableList.copyOf( userFilesDirectory.listFiles( new FilenameFilter() {
@Override
public boolean accept(final File dir, final String name) {
return name.endsWith( ".mpsites" );
}
} ) ) ).transform( new Function<File, MPUser>() {
@Nullable
@Override
public MPUser apply(final File file) {
try {
return MPSiteUnmarshaller.unmarshall( file ).getUser();
}
catch (IOException e) {
logger.err( e, "Couldn't read user from: %s", file );
return null;
}
}
} ).filter( Predicates.notNull() );
}
public void save() {
for (final MPUser user : getUsers())
try {
new CharSink() {
@Override
public Writer openStream()
throws IOException {
return new FileWriter( new File(userFilesDirectory, user.getFullName() + ".mpsites" ) );
}
}.write( MPSiteMarshaller.marshallSafe( user ).getExport() );
}
catch (IOException e) {
logger.err( e, "Unable to save sites for user: %s", user );
}
}
}

View File

@ -0,0 +1,32 @@
package com.lyndir.masterpassword.model;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Maps;
import java.util.*;
/**
* @author lhunath, 14-12-05
*/
public abstract class MPUserManager {
private final Map<String, MPUser> usersByName = Maps.newHashMap();
public MPUserManager(final Iterable<MPUser> users) {
for (MPUser user : users)
addUser( user );
}
public SortedSet<MPUser> getUsers() {
return FluentIterable.from( usersByName.values() ).toSortedSet( new Comparator<MPUser>() {
@Override
public int compare(final MPUser user1, final MPUser user2) {
return user1.getLastUsed().compareTo( user2.getLastUsed() );
}
} );
}
public void addUser(final MPUser user) {
usersByName.put( user.getFullName(), user );
}
}