diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java index 1ea12ae6..da5e8509 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MPSiteType.java @@ -181,10 +181,14 @@ public enum MPSiteType { */ public static ImmutableList forMask(final int mask) { + int typeIndex = mask & 0xF, typeMask = mask & ~0xF; + ImmutableList.Builder types = ImmutableList.builder(); - for (MPSiteType siteType : values()) - if ((siteType.getMask() & mask) != 0) + for (MPSiteType siteType : values()) { + int siteMask = siteType.getMask(), siteTypeIndex = siteMask & 0xF, siteTypeMask = siteMask & ~0xF; + if ((siteTypeMask & typeMask) != 0 && (typeIndex == 0 || siteTypeIndex == typeIndex)) types.add( siteType ); + } return types.build(); } diff --git a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java index 8c12d500..a13ed702 100644 --- a/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java +++ b/MasterPassword/Java/masterpassword-algorithm/src/main/java/com/lyndir/masterpassword/MasterKey.java @@ -52,13 +52,14 @@ public class MasterKey { String mpKeyScope = MPSiteVariant.Password.getScope(); byte[] masterKeySalt = Bytes.concat( mpKeyScope.getBytes( MP_charset ), userNameLengthBytes, userNameBytes ); logger.trc( "key scope: %s", mpKeyScope ); - logger.trc( "masterKeySalt ID: %s", idForBytes( masterKeySalt ) ); + logger.trc( "masterKeySalt ID: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); try { masterKey = SCrypt.scrypt( masterPassword.getBytes( MP_charset ), masterKeySalt, MP_N, MP_r, MP_p, MP_dkLen ); 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) { throw logger.bug( e ); @@ -70,7 +71,7 @@ public class MasterKey { return fullName; } - public String getKeyID() { + public byte[] getKeyID() { Preconditions.checkState( valid ); return idForBytes( masterKey ); @@ -111,10 +112,10 @@ public class MasterKey { siteContext == null? "(null)": siteContext ); 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 ); - logger.trc( "sitePasswordSeed ID: %s", idForBytes( sitePasswordSeed ) ); + logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( idForBytes( sitePasswordSeed ) ) ); Preconditions.checkState( sitePasswordSeed.length > 0 ); 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(); } - private static String idForBytes(final byte[] bytes) { - return CodeUtils.encodeHex( MP_hash.of( bytes ) ); + private static byte[] idForBytes(final byte[] bytes) { + return MP_hash.of( bytes ); } } diff --git a/MasterPassword/Java/masterpassword-gui/pom.xml b/MasterPassword/Java/masterpassword-gui/pom.xml index 2870ecc4..63aab06a 100644 --- a/MasterPassword/Java/masterpassword-gui/pom.xml +++ b/MasterPassword/Java/masterpassword-gui/pom.xml @@ -47,7 +47,7 @@ - com.lyndir.masterpassword.GUI + com.lyndir.masterpassword.gui.GUI diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/ConfigAuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/ConfigAuthenticationPanel.java deleted file mode 100644 index 688a6b52..00000000 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/ConfigAuthenticationPanel.java +++ /dev/null @@ -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( 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 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 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 ); - } -} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/User.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/User.java deleted file mode 100644 index 52a7e1ec..00000000 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/User.java +++ /dev/null @@ -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; - } -} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/AppleGUI.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AppleGUI.java similarity index 96% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/AppleGUI.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AppleGUI.java index b9e52213..82f010a4 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/AppleGUI.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AppleGUI.java @@ -1,4 +1,4 @@ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; import com.apple.eawt.*; import javax.swing.*; diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/AuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java similarity index 84% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/AuthenticationPanel.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java index d8ec8df6..13ea880c 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/AuthenticationPanel.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/AuthenticationPanel.java @@ -1,5 +1,6 @@ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; +import com.google.common.collect.ImmutableList; import java.awt.*; import javax.swing.*; @@ -41,7 +42,7 @@ public abstract class AuthenticationPanel extends JPanel { return null; } - public String getHelpText() { - return null; + public Iterable getButtons() { + return ImmutableList.of(); } } diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/Config.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Config.java similarity index 90% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/Config.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Config.java index d50005fa..f3f7cc85 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/Config.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Config.java @@ -1,4 +1,4 @@ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; import com.lyndir.lhunath.opal.system.util.ConversionUtils; diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/GUI.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java similarity index 93% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/GUI.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java index 856ddc99..0d6adf8b 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/GUI.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/GUI.java @@ -15,19 +15,16 @@ */ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; import com.google.common.base.Charsets; 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.util.TypeUtils; import java.io.*; import java.net.URI; import java.net.URL; import java.util.Enumeration; -import java.util.List; import java.util.jar.*; import javax.swing.*; @@ -94,20 +91,18 @@ public class GUI implements UnlockFrame.SignInCallback { SwingUtilities.invokeLater( new Runnable() { @Override public void run() { - if (passwordFrame == null) { + if (passwordFrame == null) unlockFrame.setVisible( true ); - } else { + else passwordFrame.setVisible( true ); - } } } ); } @Override public boolean signedIn(final User user) { - if (!user.hasKey()) { + if (!user.hasKey()) return false; - } user.getKey(); passwordFrame = newPasswordFrame( user ); diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/TextAuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java similarity index 60% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/TextAuthenticationPanel.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java index 9526a743..c1056205 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/TextAuthenticationPanel.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoAuthenticationPanel.java @@ -1,4 +1,4 @@ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; import java.awt.*; import java.awt.event.ActionEvent; @@ -11,35 +11,35 @@ import javax.swing.event.DocumentListener; /** * @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; - public TextAuthenticationPanel(final UnlockFrame unlockFrame) { + public IncognitoAuthenticationPanel(final UnlockFrame unlockFrame) { - // User Name + // Full Name super( unlockFrame ); - JLabel userNameLabel = new JLabel( "User Name:" ); - userNameLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); - userNameLabel.setHorizontalAlignment( SwingConstants.CENTER ); - userNameLabel.setVerticalAlignment( SwingConstants.BOTTOM ); - add( userNameLabel ); + JLabel fullNameLabel = new JLabel( "Full Name:" ); + fullNameLabel.setAlignmentX( LEFT_ALIGNMENT ); + fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER ); + fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM ); + add( fullNameLabel ); - userNameField = new JTextField() { + fullNameField = 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 ); + fullNameField.setAlignmentX( LEFT_ALIGNMENT ); + fullNameField.getDocument().addDocumentListener( this ); + fullNameField.addActionListener( this ); + add( fullNameField ); // Master Password JLabel masterPasswordLabel = new JLabel( "Master Password:" ); - masterPasswordLabel.setAlignmentX( Component.LEFT_ALIGNMENT ); + masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT ); masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER ); masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM ); add( masterPasswordLabel ); @@ -50,7 +50,7 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu return new Dimension( Integer.MAX_VALUE, getPreferredSize().height ); } }; - masterPasswordField.setAlignmentX( Component.LEFT_ALIGNMENT ); + masterPasswordField.setAlignmentX( LEFT_ALIGNMENT ); masterPasswordField.addActionListener( this ); masterPasswordField.getDocument().addDocumentListener( this ); add( masterPasswordField ); @@ -58,12 +58,12 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu @Override public Component getFocusComponent() { - return userNameField; + return fullNameField; } @Override protected User getUser() { - return new User( userNameField.getText(), new String( masterPasswordField.getPassword() ) ); + return new IncognitoUser( fullNameField.getText(), new String( masterPasswordField.getPassword() ) ); } @Override @@ -84,6 +84,6 @@ public class TextAuthenticationPanel extends AuthenticationPanel implements Docu @Override public void actionPerformed(final ActionEvent e) { updateUser( false ); - unlockFrame.trySignIn( userNameField, masterPasswordField ); + unlockFrame.trySignIn( fullNameField, masterPasswordField ); } } diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoUser.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoUser.java new file mode 100644 index 00000000..ca4fddeb --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/IncognitoUser.java @@ -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; + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java new file mode 100644 index 00000000..a0bbc48f --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelAuthenticationPanel.java @@ -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 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( 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 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() { + @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 ); + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelUser.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelUser.java new file mode 100644 index 00000000..113ed1a7 --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/ModelUser.java @@ -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; + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java similarity index 98% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java index 80641ab7..f1b6ff87 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/PasswordFrame.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/PasswordFrame.java @@ -1,8 +1,9 @@ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import com.google.common.collect.Iterables; +import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.util.Components; import java.awt.*; import java.awt.datatransfer.StringSelection; @@ -38,7 +39,7 @@ public class PasswordFrame extends JFrame implements DocumentListener { } ); // 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.setAlignmentX( LEFT_ALIGNMENT ); diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/Res.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java similarity index 97% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/Res.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java index ef85a14f..5cccafa8 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/Res.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/Res.java @@ -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.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() { return new RetinaIcon( Resources.getResource( "media/icon_question@2x.png" ) ); } diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/UnlockFrame.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java similarity index 73% rename from MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/UnlockFrame.java rename to MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java index 4c3d796b..30aca015 100644 --- a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/UnlockFrame.java +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/UnlockFrame.java @@ -1,4 +1,4 @@ -package com.lyndir.masterpassword; +package com.lyndir.masterpassword.gui; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*; @@ -18,7 +18,7 @@ public class UnlockFrame extends JFrame { private final JPanel root; private final JButton signInButton; private final JPanel authenticationContainer; - private boolean useConfig; + private boolean incognito; public User user; public UnlockFrame(final SignInCallback signInCallback) @@ -46,7 +46,6 @@ public class UnlockFrame extends JFrame { } } ); - useConfig = ConfigAuthenticationPanel.hasConfigUsers(); createAuthenticationPanel(); setLocationByPlatform( true ); @@ -65,21 +64,21 @@ public class UnlockFrame extends JFrame { authenticationContainer.removeAll(); final AuthenticationPanel authenticationPanel; - if (useConfig) { - authenticationPanel = new ConfigAuthenticationPanel( this ); + if (incognito) { + authenticationPanel = new IncognitoAuthenticationPanel( this ); } else { - authenticationPanel = new TextAuthenticationPanel( this ); + authenticationPanel = new ModelAuthenticationPanel( this ); } authenticationPanel.updateUser( false ); authenticationContainer.add( authenticationPanel, BorderLayout.CENTER ); - final JCheckBox typeCheckBox = new JCheckBox( "Use Config File" ); - typeCheckBox.setAlignmentX( LEFT_ALIGNMENT ); - typeCheckBox.setSelected( useConfig ); - typeCheckBox.addItemListener( new ItemListener() { + final JCheckBox incognitoCheckBox = new JCheckBox( "Incognito" ); + incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT ); + incognitoCheckBox.setSelected( incognito ); + incognitoCheckBox.addItemListener( new ItemListener() { @Override public void itemStateChanged(final ItemEvent e) { - useConfig = typeCheckBox.isSelected(); + incognito = incognitoCheckBox.isSelected(); SwingUtilities.invokeLater( new Runnable() { @Override public void run() { @@ -89,24 +88,15 @@ public class UnlockFrame extends JFrame { } } ); - JButton typeHelp = new JButton( Res.iconQuestion() ); - typeHelp.setMargin( new Insets( 0, 0, 0, 0 ) ); - typeHelp.setBackground( Color.red ); - typeHelp.setAlignmentX( RIGHT_ALIGNMENT ); - typeHelp.setBorder( null ); - typeHelp.addActionListener( new ActionListener() { - @Override - public void actionPerformed(final ActionEvent e) { - JOptionPane.showMessageDialog( UnlockFrame.this, authenticationPanel.getHelpText(), "Help", - JOptionPane.INFORMATION_MESSAGE ); - } - } ); - if (authenticationPanel.getHelpText() == null) { - typeHelp.setVisible( false ); + JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() ); + toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT ); + authenticationContainer.add( toolsPanel ); + for (JButton button : authenticationPanel.getButtons()) { + button.setMargin( new Insets( 0, 0, 0, 0 ) ); + button.setAlignmentX( RIGHT_ALIGNMENT ); + button.setBorder( null ); + toolsPanel.add( button ); } - JComponent typePanel = Components.boxLayout( BoxLayout.LINE_AXIS, typeCheckBox, Box.createGlue(), typeHelp ); - typePanel.setAlignmentX( Component.LEFT_ALIGNMENT ); - authenticationContainer.add( typePanel ); checkSignIn(); validate(); @@ -126,7 +116,7 @@ public class UnlockFrame extends JFrame { } boolean checkSignIn() { - boolean enabled = user != null && !user.getUserName().isEmpty() && user.hasKey(); + boolean enabled = user != null && !user.getFullName().isEmpty() && user.hasKey(); signInButton.setEnabled( enabled ); return enabled; diff --git a/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/User.java b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/User.java new file mode 100644 index 00000000..5dd9336e --- /dev/null +++ b/MasterPassword/Java/masterpassword-gui/src/main/java/com/lyndir/masterpassword/gui/User.java @@ -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(); + } +} diff --git a/MasterPassword/Java/masterpassword-gui/src/main/resources/media/icon_add.png b/MasterPassword/Java/masterpassword-gui/src/main/resources/media/icon_add.png new file mode 100644 index 00000000..401494a5 Binary files /dev/null and b/MasterPassword/Java/masterpassword-gui/src/main/resources/media/icon_add.png differ diff --git a/MasterPassword/Java/masterpassword-gui/src/main/resources/media/icon_add@2x.png b/MasterPassword/Java/masterpassword-gui/src/main/resources/media/icon_add@2x.png new file mode 100644 index 00000000..946e4a80 Binary files /dev/null and b/MasterPassword/Java/masterpassword-gui/src/main/resources/media/icon_add@2x.png differ diff --git a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteFileManager.java b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteFileManager.java deleted file mode 100644 index 611b304a..00000000 --- a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteFileManager.java +++ /dev/null @@ -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; - } -} diff --git a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteManager.java b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteManager.java deleted file mode 100644 index 5b4e1d60..00000000 --- a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteManager.java +++ /dev/null @@ -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 findSitesByName(String query) { - ImmutableList.Builder results = ImmutableList.builder(); - for (MPSite site : user.getSites()) - if (site.getSiteName().startsWith( query )) - results.add( new MPSiteResult( site ) ); - - return results.build(); - } -} diff --git a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java index 98939446..49870acd 100644 --- a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java +++ b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPSiteUnmarshaller.java @@ -31,9 +31,9 @@ public class MPSiteUnmarshaller { private static final Logger logger = Logger.get( MPSite.class ); private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTime(); private static final Pattern[] unmarshallFormats = new Pattern[]{ - Pattern.compile( "^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)" ), - Pattern.compile( "^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) }; - private static final Pattern headerFormat = Pattern.compile( "^#[[:space:]]*([^:]+): (.*)" ); + Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)? +([^\t]+)\t(.*)" ), + Pattern.compile( "^([^ ]+) +(\\d+) +(\\d+)(:\\d+)?(:\\d+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) }; + private static final Pattern headerFormat = Pattern.compile( "^#\\s*([^:]+): (.*)" ); private final int importFormat; private final int mpVersion; diff --git a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUser.java b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUser.java index 3f436f00..a6bd2abb 100644 --- a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUser.java +++ b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUser.java @@ -1,10 +1,11 @@ package com.lyndir.masterpassword.model; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Sets; import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.masterpassword.MPSiteType; import java.util.*; -import org.joda.time.DateTime; +import org.joda.time.*; /** @@ -12,18 +13,24 @@ import org.joda.time.DateTime; */ public class MPUser { - private final String fullName; - private final byte[] keyID; - private final int avatar; - private final MPSiteType defaultType; - private final DateTime lastUsed; + private final String fullName; private final Collection 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) { 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.keyID = keyID; this.avatar = avatar; @@ -31,6 +38,15 @@ public class MPUser { this.lastUsed = lastUsed; } + public Collection findSitesByName(String query) { + ImmutableList.Builder 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) { sites.add( site ); } @@ -39,6 +55,10 @@ public class MPUser { return fullName; } + public boolean hasKeyID() { + return keyID != null; + } + public boolean hasKeyID(final byte[] keyID) { return Arrays.equals( this.keyID, keyID ); } @@ -47,18 +67,34 @@ public class MPUser { return CodeUtils.encodeHex( keyID ); } + public void setKeyID(final byte[] keyID) { + this.keyID = keyID; + } + public int getAvatar() { return avatar; } + public void setAvatar(final int avatar) { + this.avatar = avatar; + } + public MPSiteType getDefaultType() { return defaultType; } - public DateTime getLastUsed() { + public void setDefaultType(final MPSiteType defaultType) { + this.defaultType = defaultType; + } + + public ReadableInstant getLastUsed() { return lastUsed; } + public void updateLastUsed() { + this.lastUsed = new Instant(); + } + public Iterable getSites() { return sites; } diff --git a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java new file mode 100644 index 00000000..9ac95730 --- /dev/null +++ b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUserFileManager.java @@ -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 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() { + @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 ); + } + } +} diff --git a/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUserManager.java b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUserManager.java new file mode 100644 index 00000000..73c62697 --- /dev/null +++ b/MasterPassword/Java/masterpassword-model/src/main/java/com/lyndir/masterpassword/model/MPUserManager.java @@ -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 usersByName = Maps.newHashMap(); + + public MPUserManager(final Iterable users) { + for (MPUser user : users) + addUser( user ); + } + + public SortedSet getUsers() { + return FluentIterable.from( usersByName.values() ).toSortedSet( new Comparator() { + @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 ); + } +}