2
0

Moar UI work on the Java app + support for per-site algorithm versioning.

This commit is contained in:
Maarten Billemont 2015-02-04 19:51:38 -05:00
parent 78c593fc08
commit a6ab9b9194
19 changed files with 262 additions and 129 deletions

View File

@ -28,6 +28,7 @@ public abstract class MasterKey {
return create( Version.CURRENT, fullName, masterPassword ); return create( Version.CURRENT, fullName, masterPassword );
} }
@Nonnull
public static MasterKey create(Version version, final String fullName, final String masterPassword) { public static MasterKey create(Version version, final String fullName, final String masterPassword) {
switch (version) { switch (version) {
@ -75,6 +76,10 @@ public abstract class MasterKey {
public abstract String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant, public abstract String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
@Nullable final String siteContext); @Nullable final String siteContext);
public boolean isValid() {
return masterKey != null;
}
public void invalidate() { public void invalidate() {
if (masterKey != null) { if (masterKey != null) {

View File

@ -100,13 +100,13 @@ public class MasterKeyV0 extends MasterKey {
logger.trc( "sitePasswordSeed ID: %s", CodeUtils.encodeHex( 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]; int templateIndex = sitePasswordSeed[0] & 0xFFFF;
MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex ); MPTemplate template = siteType.getTemplateAtRollingIndex( templateIndex );
logger.trc( "type %s, template: %s", siteType, template.getTemplateString() ); logger.trc( "type %s, template: %s", siteType, template.getTemplateString() );
StringBuilder password = new StringBuilder( template.length() ); StringBuilder password = new StringBuilder( template.length() );
for (int i = 0; i < template.length(); ++i) { for (int i = 0; i < template.length(); ++i) {
int characterIndex = sitePasswordSeed[i + 1]; int characterIndex = sitePasswordSeed[i + 1] & 0xFFFF;
MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i ); MPTemplateCharacterClass characterClass = template.getCharacterClassAtIndex( i );
char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex ); char passwordCharacter = characterClass.getCharacterAtRollingIndex( characterIndex );
logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex, logger.trc( "class %c, index %d (0x%02X) -> character: %c", characterClass.getIdentifier(), characterIndex,

View File

@ -13,7 +13,7 @@ import javax.annotation.Nullable;
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV2 extends MasterKeyV0 { public class MasterKeyV2 extends MasterKeyV1 {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV2.class ); private static final Logger logger = Logger.get( MasterKeyV2.class );

View File

@ -14,7 +14,7 @@ import javax.annotation.Nullable;
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV3 extends MasterKeyV0 { public class MasterKeyV3 extends MasterKeyV2 {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyV3.class ); private static final Logger logger = Logger.get( MasterKeyV3.class );

View File

@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.util.Components;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
@ -8,12 +9,13 @@ import javax.swing.*;
/** /**
* @author lhunath, 2014-06-11 * @author lhunath, 2014-06-11
*/ */
public abstract class AuthenticationPanel extends JPanel { public abstract class AuthenticationPanel extends Components.GradientPanel {
protected final UnlockFrame unlockFrame; protected final UnlockFrame unlockFrame;
protected final JLabel avatarLabel; protected final JLabel avatarLabel;
public AuthenticationPanel(final UnlockFrame unlockFrame) { public AuthenticationPanel(final UnlockFrame unlockFrame) {
super( null, null );
this.unlockFrame = unlockFrame; this.unlockFrame = unlockFrame;
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );

View File

@ -94,7 +94,7 @@ public class GUI implements UnlockFrame.SignInCallback {
@Override @Override
public boolean signedIn(final User user) { public boolean signedIn(final User user) {
if (!user.hasKey()) if (!user.isKeyAvailable())
return false; return false;
try { try {
user.getKey(); user.getKey();

View File

@ -24,14 +24,10 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
add( Components.stud() ); add( Components.stud() );
JLabel fullNameLabel = Components.label( "Full Name:" ); JLabel fullNameLabel = Components.label( "Full Name:" );
fullNameLabel.setAlignmentX( LEFT_ALIGNMENT );
fullNameLabel.setHorizontalAlignment( SwingConstants.CENTER );
fullNameLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( fullNameLabel ); add( fullNameLabel );
fullNameField = Components.textField(); fullNameField = Components.textField();
fullNameField.setFont( Res.valueFont().deriveFont( 12f ) ); fullNameField.setFont( Res.valueFont().deriveFont( 12f ) );
fullNameField.setAlignmentX( LEFT_ALIGNMENT );
fullNameField.getDocument().addDocumentListener( this ); fullNameField.getDocument().addDocumentListener( this );
fullNameField.addActionListener( this ); fullNameField.addActionListener( this );
add( fullNameField ); add( fullNameField );
@ -39,13 +35,9 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
// Master Password // Master Password
JLabel masterPasswordLabel = Components.label( "Master Password:" ); JLabel masterPasswordLabel = Components.label( "Master Password:" );
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel ); add( masterPasswordLabel );
masterPasswordField = Components.passwordField(); masterPasswordField = Components.passwordField();
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField ); add( masterPasswordField );

View File

@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
/** /**
@ -11,11 +12,14 @@ public class IncognitoSite extends Site {
private String siteName; private String siteName;
private MPSiteType siteType; private MPSiteType siteType;
private int siteCounter; private int siteCounter;
private MasterKey.Version algorithmVersion;
public IncognitoSite(final String siteName, final MPSiteType siteType, final int siteCounter) { public IncognitoSite(final String siteName, final MPSiteType siteType, final int siteCounter,
final MasterKey.Version algorithmVersion) {
this.siteName = siteName; this.siteName = siteName;
this.siteType = siteType; this.siteType = siteType;
this.siteCounter = siteCounter; this.siteCounter = siteCounter;
this.algorithmVersion = algorithmVersion;
} }
public String getSiteName() { public String getSiteName() {
@ -34,6 +38,16 @@ public class IncognitoSite extends Site {
this.siteType = siteType; this.siteType = siteType;
} }
@Override
public MasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
public int getSiteCounter() { public int getSiteCounter() {
return siteCounter; return siteCounter;
} }

View File

@ -11,6 +11,7 @@ public class IncognitoUser extends User {
private final String fullName; private final String fullName;
private final String masterPassword; private final String masterPassword;
private MasterKey.Version algorithmVersion;
public IncognitoUser(final String fullName, final String masterPassword) { public IncognitoUser(final String fullName, final String masterPassword) {
this.fullName = fullName; this.fullName = fullName;
@ -28,7 +29,12 @@ public class IncognitoUser extends User {
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public MasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.CURRENT; return algorithmVersion;
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
} }
@Override @Override

View File

@ -44,19 +44,10 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
// User // User
JLabel userLabel = Components.label( "User:" ); JLabel userLabel = Components.label( "User:" );
userLabel.setAlignmentX( LEFT_ALIGNMENT );
userLabel.setHorizontalAlignment( SwingConstants.CENTER );
userLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( userLabel ); add( userLabel );
userField = new JComboBox<ModelUser>( new DefaultComboBoxModel<>( readConfigUsers() ) ) { userField = Components.comboBox( readConfigUsers() );
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
userField.setFont( Res.valueFont().deriveFont( 12f ) ); userField.setFont( Res.valueFont().deriveFont( 12f ) );
userField.setAlignmentX( LEFT_ALIGNMENT );
userField.addItemListener( this ); userField.addItemListener( this );
userField.addActionListener( this ); userField.addActionListener( this );
add( userField ); add( userField );
@ -64,13 +55,9 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
// Master Password // Master Password
masterPasswordLabel = Components.label( "Master Password:" ); masterPasswordLabel = Components.label( "Master Password:" );
masterPasswordLabel.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordLabel.setHorizontalAlignment( SwingConstants.CENTER );
masterPasswordLabel.setVerticalAlignment( SwingConstants.BOTTOM );
add( masterPasswordLabel ); add( masterPasswordLabel );
masterPasswordField = Components.passwordField(); masterPasswordField = Components.passwordField();
masterPasswordField.setAlignmentX( LEFT_ALIGNMENT );
masterPasswordField.addActionListener( this ); masterPasswordField.addActionListener( this );
masterPasswordField.getDocument().addDocumentListener( this ); masterPasswordField.getDocument().addDocumentListener( this );
add( masterPasswordField ); add( masterPasswordField );

View File

@ -1,6 +1,7 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.*; import com.lyndir.masterpassword.model.*;
@ -37,6 +38,19 @@ public class ModelSite extends Site {
} }
} }
@Override
public MasterKey.Version getAlgorithmVersion() {
return model.getAlgorithmVersion();
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
if (algorithmVersion != getAlgorithmVersion()) {
model.setAlgorithmVersion( algorithmVersion );
MPUserFileManager.get().save();
}
}
public int getSiteCounter() { public int getSiteCounter() {
return model.getSiteCounter(); return model.getSiteCounter();
} }

View File

@ -42,6 +42,12 @@ public class ModelUser extends User {
return model.getAlgorithmVersion(); return model.getAlgorithmVersion();
} }
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
model.setAlgorithmVersion( algorithmVersion );
MPUserFileManager.get().save();
}
@Override @Override
public int getAvatar() { public int getAvatar() {
return model.getAvatar(); return model.getAvatar();

View File

@ -21,9 +21,11 @@ import javax.swing.event.*;
public class PasswordFrame extends JFrame implements DocumentListener { public class PasswordFrame extends JFrame implements DocumentListener {
private final User user; private final User user;
private final Components.GradientPanel root;
private final JTextField siteNameField; private final JTextField siteNameField;
private final JButton siteAddButton; private final JButton siteAddButton;
private final JComboBox<MPSiteType> siteTypeField; private final JComboBox<MPSiteType> siteTypeField;
private final JComboBox<MasterKey.Version> siteVersionField;
private final JSpinner siteCounterField; private final JSpinner siteCounterField;
private final JPasswordField passwordField; private final JPasswordField passwordField;
private final JLabel tipLabel; private final JLabel tipLabel;
@ -38,34 +40,26 @@ public class PasswordFrame extends JFrame implements DocumentListener {
super( "Master Password" ); super( "Master Password" );
this.user = user; this.user = user;
JLabel label;
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( new JPanel( new BorderLayout( 20, 20 ) ) { setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
{ root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
setBackground( Res.colors().frameBg() );
setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
}
} );
// User
add( label = Components.label( strf( "Generating passwords for: %s", user.getFullName() ) ), BorderLayout.NORTH );
label.setAlignmentX( LEFT_ALIGNMENT );
// Site // Site
JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS ); JPanel sitePanel = Components.boxLayout( BoxLayout.PAGE_AXIS );
sitePanel.setOpaque( true );
sitePanel.setBackground( Res.colors().controlBg() ); sitePanel.setBackground( Res.colors().controlBg() );
sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) ); sitePanel.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.bordered( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER ); add( Components.bordered( sitePanel, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ), BorderLayout.CENTER );
// Site Name // User
sitePanel.add( label = Components.label( "Site Name:" ) ); sitePanel.add( Components.label( strf( "Generating passwords for: %s", user.getFullName() ), JLabel.CENTER ) );
label.setAlignmentX( LEFT_ALIGNMENT ); sitePanel.add( Components.stud() );
// Site Name
sitePanel.add( Components.label( "Site Name:" ) );
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, // JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteNameField = Components.textField(), Components.stud(), siteNameField = Components.textField(), Components.stud(),
siteAddButton = Components.button( "Add Site" ) ); siteAddButton = Components.button( "Add Site" ) );
siteNameField.setAlignmentX( LEFT_ALIGNMENT );
siteNameField.getDocument().addDocumentListener( this ); siteNameField.getDocument().addDocumentListener( this );
siteNameField.addActionListener( new ActionListener() { siteNameField.addActionListener( new ActionListener() {
@Override @Override
@ -94,8 +88,6 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} }
} ); } );
siteAddButton.setVisible( false ); siteAddButton.setVisible( false );
siteAddButton.setAlignmentX( RIGHT_ALIGNMENT );
siteAddButton.setAlignmentY( CENTER_ALIGNMENT );
siteAddButton.addActionListener( new ActionListener() { siteAddButton.addActionListener( new ActionListener() {
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
@ -103,23 +95,20 @@ public class PasswordFrame extends JFrame implements DocumentListener {
siteAddButton.setVisible( false ); siteAddButton.setVisible( false );
} }
} ); } );
siteControls.setBackground( null );
siteControls.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteControls ); sitePanel.add( siteControls );
sitePanel.add( Components.stud() );
// Site Type & Counter // Site Type & Counter
MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class ); MPSiteType[] types = Iterables.toArray( MPSiteType.forClass( MPSiteTypeClass.Generated ), MPSiteType.class );
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, // JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
siteTypeField = new JComboBox<>( types ), // siteTypeField = Components.comboBox( types ), //
Components.stud(), //
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
Components.stud(), // Components.stud(), //
siteCounterField = Components.spinner( siteCounterField = Components.spinner(
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) ); new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) );
siteSettings.setBackground( null );
siteSettings.setAlignmentX( LEFT_ALIGNMENT );
sitePanel.add( siteSettings ); sitePanel.add( siteSettings );
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) ); siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
siteTypeField.setAlignmentX( LEFT_ALIGNMENT );
siteTypeField.setAlignmentY( CENTER_ALIGNMENT );
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong ); siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
siteTypeField.addItemListener( new ItemListener() { siteTypeField.addItemListener( new ItemListener() {
@Override @Override
@ -128,9 +117,18 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} }
} ); } );
siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) );
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
siteVersionField.setSelectedItem( user.getAlgorithmVersion() );
siteVersionField.addItemListener( new ItemListener() {
@Override
public void itemStateChanged(final ItemEvent e) {
updatePassword();
}
} );
siteCounterField.setFont( Res.valueFont().deriveFont( 12f ) ); siteCounterField.setFont( Res.valueFont().deriveFont( 12f ) );
siteCounterField.setAlignmentX( RIGHT_ALIGNMENT ); siteCounterField.setAlignmentX( RIGHT_ALIGNMENT );
siteCounterField.setAlignmentY( CENTER_ALIGNMENT );
siteCounterField.addChangeListener( new ChangeListener() { siteCounterField.addChangeListener( new ChangeListener() {
@Override @Override
public void stateChanged(final ChangeEvent e) { public void stateChanged(final ChangeEvent e) {
@ -151,9 +149,11 @@ public class PasswordFrame extends JFrame implements DocumentListener {
// Password // Password
passwordField = new JPasswordField(); passwordField = new JPasswordField();
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
passwordField.setEditable( false ); passwordField.setEditable( false );
passwordField.setHorizontalAlignment( JTextField.CENTER ); passwordField.setHorizontalAlignment( JTextField.CENTER );
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true ); passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
passwordField.setBorder( null );
passwordEchoChar = passwordField.getEchoChar(); passwordEchoChar = passwordField.getEchoChar();
passwordEchoFont = passwordField.getFont().deriveFont( 40f ); passwordEchoFont = passwordField.getFont().deriveFont( 40f );
updateMask(); updateMask();
@ -161,16 +161,15 @@ public class PasswordFrame extends JFrame implements DocumentListener {
// Tip // Tip
tipLabel = Components.label( " ", JLabel.CENTER ); tipLabel = Components.label( " ", JLabel.CENTER );
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT ); tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordField, tipLabel );
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, passwordContainer.setOpaque( true );
Components.bordered( passwordField, BorderFactory.createLoweredSoftBevelBorder(), passwordContainer.setBackground( Color.white );
Res.colors().frameBg() ), tipLabel ); passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
passwordContainer.setBackground( null ); add( Components.bordered( passwordContainer, BorderFactory.createLoweredSoftBevelBorder(), Res.colors().frameBg() ),
add( passwordContainer, BorderLayout.SOUTH ); BorderLayout.SOUTH );
pack(); pack();
setMinimumSize( getSize() ); setMinimumSize( new Dimension( Math.max( 600, getPreferredSize().width ), Math.max( 300, getPreferredSize().height ) ) );
setPreferredSize( new Dimension( 600, getSize().height ) );
pack(); pack();
setLocationByPlatform( true ); setLocationByPlatform( true );
@ -188,19 +187,21 @@ public class PasswordFrame extends JFrame implements DocumentListener {
final String siteNameQuery = siteNameField.getText(); final String siteNameQuery = siteNameField.getText();
if (updatingUI) if (updatingUI)
return Futures.immediateCancelledFuture(); return Futures.immediateCancelledFuture();
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.hasKey()) { if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
tipLabel.setText( null ); tipLabel.setText( null );
passwordField.setText( null ); passwordField.setText( null );
return Futures.immediateCancelledFuture(); return Futures.immediateCancelledFuture();
} }
MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() ); final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
final int siteCounter = (Integer) siteCounterField.getValue(); final int siteCounter = (Integer) siteCounterField.getValue();
final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite final Site site = currentSite != null && currentSite.getSiteName().equals( siteNameQuery )? currentSite
: Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter ) ); : Iterables.getFirst( user.findSitesByName( siteNameQuery ), new IncognitoSite( siteNameQuery, siteType, siteCounter, user.getAlgorithmVersion() ) );
assert site != null; assert site != null;
if (site == currentSite) { if (site == currentSite) {
site.setSiteType( siteType ); site.setSiteType( siteType );
site.setAlgorithmVersion( siteVersion );
site.setSiteCounter( siteCounter ); site.setSiteCounter( siteCounter );
} }
@ -208,7 +209,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
@Override @Override
public String call() public String call()
throws Exception { throws Exception {
return user.getKey().encode( site.getSiteName(), site.getSiteType(), site.getSiteCounter(), MPSiteVariant.Password, null ); return user.getKey( site.getAlgorithmVersion() )
.encode( site.getSiteName(), site.getSiteType(), site.getSiteCounter(), MPSiteVariant.Password, null );
} }
} ); } );
Futures.addCallback( passwordFuture, new FutureCallback<String>() { Futures.addCallback( passwordFuture, new FutureCallback<String>() {
@ -221,6 +223,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
currentSite = site; currentSite = site;
siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) ); siteAddButton.setVisible( user instanceof ModelUser && !(currentSite instanceof ModelSite) );
siteTypeField.setSelectedItem( currentSite.getSiteType() ); siteTypeField.setSelectedItem( currentSite.getSiteType() );
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
siteCounterField.setValue( currentSite.getSiteCounter() ); siteCounterField.setValue( currentSite.getSiteCounter() );
siteNameField.setText( currentSite.getSiteName() ); siteNameField.setText( currentSite.getSiteName() );
if (siteNameField.getText().startsWith( siteNameQuery )) if (siteNameField.getText().startsWith( siteNameQuery ))

View File

@ -3,6 +3,7 @@ package com.lyndir.masterpassword.gui;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.MPSiteType; import com.lyndir.masterpassword.MPSiteType;
import com.lyndir.masterpassword.MasterKey;
/** /**
@ -18,6 +19,10 @@ public abstract class Site {
public abstract void setSiteType(final MPSiteType siteType); public abstract void setSiteType(final MPSiteType siteType);
public abstract MasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
public abstract int getSiteCounter(); public abstract int getSiteCounter();
public abstract void setSiteCounter(final int siteCounter); public abstract void setSiteCounter(final int siteCounter);

View File

@ -6,7 +6,6 @@ import com.lyndir.masterpassword.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.*;
/** /**
@ -15,7 +14,7 @@ import javax.swing.border.*;
public class UnlockFrame extends JFrame { public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback; private final SignInCallback signInCallback;
private final JPanel root; private final Components.GradientPanel root;
private final JButton signInButton; private final JButton signInButton;
private final JPanel authenticationContainer; private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel; private AuthenticationPanel authenticationPanel;
@ -28,11 +27,11 @@ public class UnlockFrame extends JFrame {
this.signInCallback = signInCallback; this.signInCallback = signInCallback;
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
setContentPane( root = new JPanel( new BorderLayout( 20, 20 ) ) ); setContentPane( root = Components.gradientPanel( new BorderLayout( 20, 20 ), Res.colors().frameBg() ) );
root.setBackground( Res.colors().frameBg() ); root.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
root.setBorder( new EmptyBorder( 20, 20, 20, 20 ) );
authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS ); authenticationContainer = Components.boxLayout( BoxLayout.PAGE_AXIS );
authenticationContainer.setOpaque( true );
authenticationContainer.setBackground( Res.colors().controlBg() ); authenticationContainer.setBackground( Res.colors().controlBg() );
authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) ); authenticationContainer.setBorder( BorderFactory.createEmptyBorder( 20, 20, 20, 20 ) );
add( Components.bordered( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) ); add( Components.bordered( authenticationContainer, BorderFactory.createRaisedBevelBorder(), Res.colors().frameBg() ) );
@ -42,7 +41,6 @@ public class UnlockFrame extends JFrame {
Box.createGlue() ); Box.createGlue() );
signInBox.setBackground( null ); signInBox.setBackground( null );
root.add( signInBox, BorderLayout.SOUTH ); root.add( signInBox, 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) {
@ -75,7 +73,6 @@ public class UnlockFrame extends JFrame {
authenticationContainer.add( Components.stud() ); authenticationContainer.add( Components.stud() );
final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" ); final JCheckBox incognitoCheckBox = Components.checkBox( "Incognito" );
incognitoCheckBox.setAlignmentX( LEFT_ALIGNMENT );
incognitoCheckBox.setSelected( incognito ); incognitoCheckBox.setSelected( incognito );
incognitoCheckBox.addItemListener( new ItemListener() { incognitoCheckBox.addItemListener( new ItemListener() {
@Override @Override
@ -91,7 +88,6 @@ public class UnlockFrame extends JFrame {
} ); } );
JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() ); JComponent toolsPanel = Components.boxLayout( BoxLayout.LINE_AXIS, incognitoCheckBox, Box.createGlue() );
toolsPanel.setAlignmentX( Component.LEFT_ALIGNMENT );
authenticationContainer.add( toolsPanel ); authenticationContainer.add( toolsPanel );
for (JButton button : authenticationPanel.getButtons()) { for (JButton button : authenticationPanel.getButtons()) {
button.setMargin( new Insets( 0, 0, 0, 0 ) ); button.setMargin( new Insets( 0, 0, 0, 0 ) );
@ -118,7 +114,7 @@ public class UnlockFrame extends JFrame {
} }
boolean checkSignIn() { boolean checkSignIn() {
boolean enabled = user != null && !user.getFullName().isEmpty() && user.hasKey(); boolean enabled = user != null && !user.getFullName().isEmpty() && user.isKeyAvailable();
signInButton.setEnabled( enabled ); signInButton.setEnabled( enabled );
return enabled; return enabled;

View File

@ -2,9 +2,9 @@ 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.Maps;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.MPUser; import java.util.EnumMap;
import java.security.KeyException;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -14,7 +14,8 @@ import javax.annotation.Nonnull;
*/ */
public abstract class User { public abstract class User {
private MasterKey key; @Nonnull
private static final EnumMap<MasterKey.Version, MasterKey> keyByVersion = Maps.newEnumMap( MasterKey.Version.class );
public abstract String getFullName(); public abstract String getFullName();
@ -22,32 +23,42 @@ public abstract class User {
public abstract MasterKey.Version getAlgorithmVersion(); public abstract MasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
public int getAvatar() { public int getAvatar() {
return 0; return 0;
} }
public boolean hasKey() { public boolean isKeyAvailable() {
String masterPassword = getMasterPassword(); String masterPassword = getMasterPassword();
return key != null || (masterPassword != null && !masterPassword.isEmpty()); return masterPassword != null && !masterPassword.isEmpty();
} }
@Nonnull @Nonnull
public MasterKey getKey() throws MasterKeyException { public MasterKey getKey() throws MasterKeyException {
if (key == null) { return getKey( getAlgorithmVersion() );
}
@Nonnull
public MasterKey getKey(MasterKey.Version algorithmVersion) throws MasterKeyException {
String masterPassword = getMasterPassword(); String masterPassword = getMasterPassword();
if (masterPassword == null || masterPassword.isEmpty()) { if (masterPassword == null || masterPassword.isEmpty()) {
reset(); reset();
throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) ); throw new MasterKeyException( strf( "Master password unknown for user: %s", getFullName() ) );
} }
key = MasterKey.create( getAlgorithmVersion(), getFullName(), masterPassword ); MasterKey key = keyByVersion.get( algorithmVersion );
} if (key == null)
keyByVersion.put( algorithmVersion, key = MasterKey.create( algorithmVersion, getFullName(), masterPassword ) );
if (!key.isValid())
key.revalidate( masterPassword );
return key; return key;
} }
public void reset() { public void reset() {
key = null; for (MasterKey key : keyByVersion.values())
key.invalidate();
} }
public abstract Iterable<Site> findSitesByName(final String siteName); public abstract Iterable<Site> findSitesByName(final String siteName);

View File

@ -1,5 +1,6 @@
package com.lyndir.masterpassword.util; package com.lyndir.masterpassword.util;
import com.lyndir.masterpassword.gui.ModelUser;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import java.awt.*; import java.awt.*;
import javax.swing.*; import javax.swing.*;
@ -12,8 +13,9 @@ import javax.swing.border.CompoundBorder;
*/ */
public abstract class Components { public abstract class Components {
public static JPanel boxLayout(int axis, Component... components) { public static GradientPanel boxLayout(int axis, Component... components) {
JPanel container = new JPanel(); GradientPanel container = gradientPanel( null, null );
// container.setBackground( Color.red );
container.setLayout( new BoxLayout( container, axis ) ); container.setLayout( new BoxLayout( container, axis ) );
for (Component component : components) for (Component component : components)
container.add( component ); container.add( component );
@ -21,12 +23,12 @@ public abstract class Components {
return container; return container;
} }
public static JPanel bordered(final JComponent component, final Border border) { public static GradientPanel bordered(final JComponent component, final Border border) {
return bordered( component, border, null ); return bordered( component, border, null );
} }
public static JPanel bordered(final JComponent component, final Border border, Color background) { public static GradientPanel bordered(final JComponent component, final Border border, Color background) {
JPanel box = boxLayout( BoxLayout.LINE_AXIS, component ); GradientPanel box = boxLayout( BoxLayout.LINE_AXIS, component );
if (border != null) if (border != null)
box.setBorder( border ); box.setBorder( border );
@ -37,12 +39,25 @@ public abstract class Components {
return box; return box;
} }
public static GradientPanel gradientPanel(final LayoutManager layout, final Color color) {
return new GradientPanel( layout, color ) {
{
setOpaque( color != null );
setBackground( null );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
};
}
public static JTextField textField() { public static JTextField textField() {
return new JTextField() { return new JTextField() {
{ {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setFont( Res.valueFont().deriveFont( 12f ) ); setFont( Res.valueFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@ -57,6 +72,8 @@ public abstract class Components {
{ {
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@ -70,6 +87,8 @@ public abstract class Components {
return new JButton( label ) { return new JButton( label ) {
{ {
setFont( Res.controlFont().deriveFont( 12f ) ); setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@ -80,7 +99,12 @@ public abstract class Components {
} }
public static Component stud() { public static Component stud() {
return Box.createRigidArea( new Dimension( 8, 8 ) ); Dimension studDimension = new Dimension( 8, 8 );
Box.Filler rigidArea = new Box.Filler( studDimension, studDimension, studDimension );
rigidArea.setAlignmentX( Component.LEFT_ALIGNMENT );
rigidArea.setAlignmentY( Component.BOTTOM_ALIGNMENT );
rigidArea.setBackground( Color.red );
return rigidArea;
} }
public static JSpinner spinner(final SpinnerModel model) { public static JSpinner spinner(final SpinnerModel model) {
@ -90,6 +114,8 @@ public abstract class Components {
BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ), BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ); BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) );
((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder ); ((DefaultEditor) getEditor()).getTextField().setBorder( editorBorder );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
@Override @Override
@ -107,6 +133,13 @@ public abstract class Components {
return new JLabel( label, alignment ) { return new JLabel( label, alignment ) {
{ {
setFont( Res.controlFont().deriveFont( 12f ) ); setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
} }
}; };
} }
@ -115,7 +148,67 @@ public abstract class Components {
return new JCheckBox( label ) { return new JCheckBox( label ) {
{ {
setFont( Res.controlFont().deriveFont( 12f ) ); setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
} }
}; };
} }
@SafeVarargs
public static <V> JComboBox<V> comboBox(final V... values) {
return comboBox( new DefaultComboBoxModel<>( values ) );
}
public static <M> JComboBox<M> comboBox(final ComboBoxModel<M> model) {
return new JComboBox<M>( model ) {
{
setFont( Res.controlFont().deriveFont( 12f ) );
setAlignmentX( LEFT_ALIGNMENT );
setAlignmentY( BOTTOM_ALIGNMENT );
}
@Override
public Dimension getMaximumSize() {
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
}
};
}
public static class GradientPanel extends JPanel {
private Color gradientColor;
private GradientPaint paint;
protected GradientPanel(final LayoutManager layout, final Color gradientColor) {
super( layout );
this.gradientColor = gradientColor;
setBackground( null );
}
public Color getGradientColor() {
return gradientColor;
}
public void setGradientColor(final Color gradientColor) {
this.gradientColor = gradientColor;
}
@Override
public void doLayout() {
super.doLayout();
if (gradientColor != null)
paint = new GradientPaint( new Point( 0, 0 ), gradientColor, new Point( getWidth(), getHeight() ), gradientColor.darker() );
}
@Override
protected void paintComponent(final Graphics g) {
super.paintComponent( g );
if (paint != null) {
((Graphics2D) g).setPaint( paint );
g.fillRect( 0, 0, getWidth(), getHeight() );
}
}
}
} }

View File

@ -6,7 +6,6 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.joda.time.DateTime;
import org.joda.time.Instant; import org.joda.time.Instant;
@ -19,7 +18,7 @@ public class MPSite {
public static final int DEFAULT_COUNTER = 1; public static final int DEFAULT_COUNTER = 1;
private final MPUser user; private final MPUser user;
private MasterKey.Version mpVersion; private MasterKey.Version algorithmVersion;
private Instant lastUsed; private Instant lastUsed;
private String siteName; private String siteName;
private MPSiteType siteType; private MPSiteType siteType;
@ -33,17 +32,17 @@ public class MPSite {
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final int siteCounter) { public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final int siteCounter) {
this.user = user; this.user = user;
this.mpVersion = MasterKey.Version.CURRENT; this.algorithmVersion = MasterKey.Version.CURRENT;
this.lastUsed = new Instant(); this.lastUsed = new Instant();
this.siteName = siteName; this.siteName = siteName;
this.siteType = siteType; this.siteType = siteType;
this.siteCounter = siteCounter; this.siteCounter = siteCounter;
} }
protected MPSite(final MPUser user, final MasterKey.Version mpVersion, final Instant lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter, protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter,
final int uses, final String loginName, final String importContent) { final int uses, final String loginName, final String importContent) {
this.user = user; this.user = user;
this.mpVersion = mpVersion; this.algorithmVersion = algorithmVersion;
this.lastUsed = lastUsed; this.lastUsed = lastUsed;
this.siteName = siteName; this.siteName = siteName;
this.siteType = siteType; this.siteType = siteType;
@ -69,12 +68,12 @@ public class MPSite {
return null; return null;
} }
public MasterKey.Version getMPVersion() { public MasterKey.Version getAlgorithmVersion() {
return mpVersion; return algorithmVersion;
} }
public void setMPVersion(final MasterKey.Version mpVersion) { public void setAlgorithmVersion(final MasterKey.Version mpVersion) {
this.mpVersion = mpVersion; this.algorithmVersion = mpVersion;
} }
public Instant getLastUsed() { public Instant getLastUsed() {

View File

@ -82,7 +82,7 @@ public class MPSiteMarshaller {
site.getUses(), // uses site.getUses(), // uses
strf( "%d:%d:%d", // strf( "%d:%d:%d", //
site.getSiteType().getType(), // type site.getSiteType().getType(), // type
site.getMPVersion(), // algorithm site.getAlgorithmVersion(), // algorithm
site.getSiteCounter() ), // counter site.getSiteCounter() ), // counter
ifNotNullElse( site.getLoginName(), "" ), // loginName ifNotNullElse( site.getLoginName(), "" ), // loginName
site.getSiteName(), // siteName site.getSiteName(), // siteName