Implement security answers & immediate site lookup.
This commit is contained in:
parent
7d1aa9c9f4
commit
10c6d203b8
@ -36,7 +36,7 @@ _needs() {
|
|||||||
IFS=: read pkg tools <<< "$spec"
|
IFS=: read pkg tools <<< "$spec"
|
||||||
IFS=, read -a tools <<< "${tools:-$pkg}"
|
IFS=, read -a tools <<< "${tools:-$pkg}"
|
||||||
for tool in "${tools[@]}"; do
|
for tool in "${tools[@]}"; do
|
||||||
hash "$tool" && continue 2
|
hash "$tool" 2>/dev/null && continue 2
|
||||||
done
|
done
|
||||||
|
|
||||||
echo >&2 "Missing: $pkg. Please install this package."
|
echo >&2 "Missing: $pkg. Please install this package."
|
||||||
|
@ -31,17 +31,7 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public class MPIncognitoQuestion extends MPBasicQuestion {
|
public class MPIncognitoQuestion extends MPBasicQuestion {
|
||||||
|
|
||||||
private final MPIncognitoSite site;
|
|
||||||
|
|
||||||
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
|
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
|
||||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||||
|
|
||||||
this.site = site;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public MPIncognitoSite getSite() {
|
|
||||||
return site;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,4 +40,10 @@ public class MPIncognitoSite extends MPBasicSite<MPIncognitoUser, MPIncognitoQue
|
|||||||
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
|
@Nullable final MPResultType resultType, @Nullable final MPResultType loginType) {
|
||||||
super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
|
super( user, siteName, (algorithm == null)? user.getAlgorithm(): algorithm, counter, resultType, loginType );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public MPIncognitoQuestion addQuestion(final String keyword) {
|
||||||
|
return addQuestion( new MPIncognitoQuestion( this, keyword, null ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ package com.lyndir.masterpassword.gui.model;
|
|||||||
|
|
||||||
import com.lyndir.masterpassword.MPAlgorithm;
|
import com.lyndir.masterpassword.MPAlgorithm;
|
||||||
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
@ -38,6 +39,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPIncognitoSite addSite(final String siteName) {
|
public MPIncognitoSite addSite(final String siteName) {
|
||||||
return addSite( new MPIncognitoSite( this, siteName ) );
|
return addSite( new MPIncognitoSite( this, siteName ) );
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.model;
|
||||||
|
|
||||||
|
import com.lyndir.masterpassword.model.MPSite;
|
||||||
|
import com.lyndir.masterpassword.model.impl.MPBasicQuestion;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-27
|
||||||
|
*/
|
||||||
|
public class MPNewQuestion extends MPBasicQuestion {
|
||||||
|
|
||||||
|
public MPNewQuestion(final MPSite<?> site, final String keyword) {
|
||||||
|
super( site, keyword, site.getAlgorithm().mpw_default_answer_type() );
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ package com.lyndir.masterpassword.gui.model;
|
|||||||
|
|
||||||
import com.lyndir.masterpassword.model.*;
|
import com.lyndir.masterpassword.model.*;
|
||||||
import com.lyndir.masterpassword.model.impl.*;
|
import com.lyndir.masterpassword.model.impl.*;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -12,4 +13,10 @@ public class MPNewSite extends MPBasicSite<MPUser<?>, MPQuestion> {
|
|||||||
public MPNewSite(final MPUser<?> user, final String siteName) {
|
public MPNewSite(final MPUser<?> user, final String siteName) {
|
||||||
super( user, siteName );
|
super( user, siteName );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public MPQuestion addQuestion(final String keyword) {
|
||||||
|
throw new UnsupportedOperationException( "Cannot add a question to a site that hasn't been created yet." );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ public abstract class Components {
|
|||||||
|
|
||||||
public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
|
public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) {
|
||||||
JDialog dialog = pane.createDialog( owner, title );
|
JDialog dialog = pane.createDialog( owner, title );
|
||||||
|
dialog.setMinimumSize( new Dimension( 520, 0 ) );
|
||||||
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
|
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
|
||||||
showDialog( dialog );
|
showDialog( dialog );
|
||||||
|
|
||||||
@ -143,7 +144,6 @@ public abstract class Components {
|
|||||||
title, Dialog.ModalityType.DOCUMENT_MODAL );
|
title, Dialog.ModalityType.DOCUMENT_MODAL );
|
||||||
dialog.setMinimumSize( new Dimension( 320, 0 ) );
|
dialog.setMinimumSize( new Dimension( 320, 0 ) );
|
||||||
dialog.setLocationRelativeTo( owner );
|
dialog.setLocationRelativeTo( owner );
|
||||||
dialog.setLocationByPlatform( true );
|
|
||||||
dialog.setContentPane( content );
|
dialog.setContentPane( content );
|
||||||
|
|
||||||
return showDialog( dialog );
|
return showDialog( dialog );
|
||||||
@ -155,6 +155,7 @@ public abstract class Components {
|
|||||||
dialog.getRootPane().putClientProperty( "Window.style", "small" );
|
dialog.getRootPane().putClientProperty( "Window.style", "small" );
|
||||||
dialog.pack();
|
dialog.pack();
|
||||||
|
|
||||||
|
dialog.setLocationByPlatform( true );
|
||||||
dialog.setVisible( true );
|
dialog.setVisible( true );
|
||||||
|
|
||||||
return dialog;
|
return dialog;
|
||||||
@ -176,7 +177,7 @@ public abstract class Components {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> selection) {
|
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> change) {
|
||||||
return new JTextField( text ) {
|
return new JTextField( text ) {
|
||||||
{
|
{
|
||||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||||
@ -184,23 +185,25 @@ public abstract class Components {
|
|||||||
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
||||||
setAlignmentX( LEFT_ALIGNMENT );
|
setAlignmentX( LEFT_ALIGNMENT );
|
||||||
|
|
||||||
if (selection != null)
|
if (change != null) {
|
||||||
getDocument().addDocumentListener( new DocumentListener() {
|
getDocument().addDocumentListener( new DocumentListener() {
|
||||||
@Override
|
@Override
|
||||||
public void insertUpdate(final DocumentEvent e) {
|
public void insertUpdate(final DocumentEvent e) {
|
||||||
selection.accept( getText() );
|
change.accept( getText() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeUpdate(final DocumentEvent e) {
|
public void removeUpdate(final DocumentEvent e) {
|
||||||
selection.accept( getText() );
|
change.accept( getText() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changedUpdate(final DocumentEvent e) {
|
public void changedUpdate(final DocumentEvent e) {
|
||||||
selection.accept( getText() );
|
change.accept( getText() );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
change.accept( getText() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -228,6 +231,7 @@ public abstract class Components {
|
|||||||
public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
|
public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
|
||||||
return new JList<E>( model ) {
|
return new JList<E>( model ) {
|
||||||
{
|
{
|
||||||
|
setAlignmentX( LEFT_ALIGNMENT );
|
||||||
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
||||||
setCellRenderer( new DefaultListCellRenderer() {
|
setCellRenderer( new DefaultListCellRenderer() {
|
||||||
@Override
|
@Override
|
||||||
@ -241,7 +245,9 @@ public abstract class Components {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
setAlignmentX( LEFT_ALIGNMENT );
|
|
||||||
|
if (model instanceof CollectionListModel)
|
||||||
|
((CollectionListModel<E>) model).registerList( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,8 +9,6 @@ import com.lyndir.masterpassword.model.MPUser;
|
|||||||
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
||||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
@ -26,8 +24,6 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
|||||||
|
|
||||||
private final CollectionListModel<MPUser<?>> usersModel =
|
private final CollectionListModel<MPUser<?>> usersModel =
|
||||||
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
||||||
private final JComboBox<? extends MPUser<?>> userField =
|
|
||||||
Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) );
|
|
||||||
|
|
||||||
protected FilesPanel() {
|
protected FilesPanel() {
|
||||||
setOpaque( false );
|
setOpaque( false );
|
||||||
@ -46,7 +42,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
|||||||
add( Components.strut( Components.margin() ) );
|
add( Components.strut( Components.margin() ) );
|
||||||
|
|
||||||
// User Selection
|
// User Selection
|
||||||
add( userField );
|
add( Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) ) );
|
||||||
|
|
||||||
MPFileUserManager.get().addListener( this );
|
MPFileUserManager.get().addListener( this );
|
||||||
MasterPassword.get().addListener( this );
|
MasterPassword.get().addListener( this );
|
||||||
|
@ -3,12 +3,10 @@ package com.lyndir.masterpassword.gui.view;
|
|||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.masterpassword.gui.util.Components;
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
import com.lyndir.masterpassword.gui.util.Res;
|
import com.lyndir.masterpassword.gui.util.Res;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
|
||||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ComponentAdapter;
|
import java.awt.event.ComponentAdapter;
|
||||||
import java.awt.event.ComponentEvent;
|
import java.awt.event.ComponentEvent;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.BevelBorder;
|
import javax.swing.border.BevelBorder;
|
||||||
|
|
||||||
@ -23,8 +21,6 @@ public class MasterPasswordFrame extends JFrame {
|
|||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private final Components.GradientPanel root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS );
|
private final Components.GradientPanel root = Components.borderPanel( Res.colors().frameBg(), BoxLayout.PAGE_AXIS );
|
||||||
private final FilesPanel filesPanel = new FilesPanel();
|
|
||||||
private final JPanel userPanel = Components.panel( new BorderLayout( 0, 0 ) );
|
|
||||||
private final UserContentPanel userContent = new UserContentPanel();
|
private final UserContentPanel userContent = new UserContentPanel();
|
||||||
|
|
||||||
@SuppressWarnings("MagicNumber")
|
@SuppressWarnings("MagicNumber")
|
||||||
@ -32,15 +28,16 @@ public class MasterPasswordFrame extends JFrame {
|
|||||||
super( "Master Password" );
|
super( "Master Password" );
|
||||||
|
|
||||||
setContentPane( root );
|
setContentPane( root );
|
||||||
root.add( filesPanel );
|
root.add( new FilesPanel() );
|
||||||
root.add( Components.strut() );
|
root.add( Components.strut() );
|
||||||
root.add( userPanel );
|
|
||||||
|
|
||||||
|
JPanel userPanel = Components.panel( new BorderLayout( 0, 0 ) );
|
||||||
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
|
userPanel.add( userContent.getUserToolbar(), BorderLayout.LINE_START );
|
||||||
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
|
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
|
||||||
userPanel.add( Components.borderPanel(
|
userPanel.add( Components.borderPanel(
|
||||||
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
|
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
|
||||||
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
|
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
|
||||||
|
root.add( userPanel );
|
||||||
|
|
||||||
addComponentListener( new ComponentHandler() );
|
addComponentListener( new ComponentHandler() );
|
||||||
setPreferredSize( new Dimension( 800, 560 ) );
|
setPreferredSize( new Dimension( 800, 560 ) );
|
||||||
|
@ -10,8 +10,7 @@ import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
|||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.gui.MPGuiConstants;
|
import com.lyndir.masterpassword.gui.MPGuiConstants;
|
||||||
import com.lyndir.masterpassword.gui.MasterPassword;
|
import com.lyndir.masterpassword.gui.MasterPassword;
|
||||||
import com.lyndir.masterpassword.gui.model.MPIncognitoUser;
|
import com.lyndir.masterpassword.gui.model.*;
|
||||||
import com.lyndir.masterpassword.gui.model.MPNewSite;
|
|
||||||
import com.lyndir.masterpassword.gui.util.*;
|
import com.lyndir.masterpassword.gui.util.*;
|
||||||
import com.lyndir.masterpassword.gui.util.Platform;
|
import com.lyndir.masterpassword.gui.util.Platform;
|
||||||
import com.lyndir.masterpassword.model.*;
|
import com.lyndir.masterpassword.model.*;
|
||||||
@ -290,9 +289,9 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
|
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
|
||||||
"Change the master password for this user." );
|
"Change the master password for this user." );
|
||||||
|
|
||||||
private final JPasswordField masterPasswordField = Components.passwordField();
|
private final JPasswordField masterPasswordField;
|
||||||
private final JLabel errorLabel = Components.label();
|
private final JLabel errorLabel;
|
||||||
private final JLabel identiconLabel = Components.label( SwingConstants.CENTER );
|
private final JLabel identiconLabel;
|
||||||
|
|
||||||
private Future<?> identiconJob;
|
private Future<?> identiconJob;
|
||||||
|
|
||||||
@ -312,16 +311,16 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||||
add( Components.strut() );
|
add( Components.strut() );
|
||||||
|
|
||||||
add( identiconLabel );
|
add( identiconLabel = Components.label( SwingConstants.CENTER ) );
|
||||||
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
|
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
|
|
||||||
add( Components.label( "Master Password:" ) );
|
add( Components.label( "Master Password:" ) );
|
||||||
add( Components.strut() );
|
add( Components.strut() );
|
||||||
add( masterPasswordField );
|
add( masterPasswordField = Components.passwordField() );
|
||||||
masterPasswordField.addActionListener( this );
|
masterPasswordField.addActionListener( this );
|
||||||
masterPasswordField.getDocument().addDocumentListener( this );
|
masterPasswordField.getDocument().addDocumentListener( this );
|
||||||
add( errorLabel );
|
add( errorLabel = Components.label() );
|
||||||
errorLabel.setForeground( Res.colors().errorFg() );
|
errorLabel.setForeground( Res.colors().errorFg() );
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
}
|
}
|
||||||
@ -468,21 +467,20 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
"Sign out and lock user." );
|
"Sign out and lock user." );
|
||||||
private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings(),
|
private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings(),
|
||||||
"Show site settings." );
|
"Show site settings." );
|
||||||
private final JButton questionsButton = Components.button( Res.icons().question(), null,
|
private final JButton questionsButton = Components.button( Res.icons().question(), event -> showSiteQuestions(),
|
||||||
"Show site recovery questions." );
|
"Show site recovery questions." );
|
||||||
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(),
|
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(),
|
||||||
"Delete the site from the user." );
|
"Delete the site from the user." );
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
private final MPUser<?> user;
|
private final MPUser<?> user;
|
||||||
private final JLabel passwordLabel = Components.label( SwingConstants.CENTER );
|
private final JLabel passwordLabel;
|
||||||
private final JLabel passwordField = Components.heading( SwingConstants.CENTER );
|
private final JLabel passwordField;
|
||||||
private final JLabel queryLabel = Components.label();
|
private final JLabel answerField;
|
||||||
private final JTextField queryField = Components.textField( null, this::updateSites );
|
private final JLabel queryLabel;
|
||||||
private final CollectionListModel<MPSite<?>> sitesModel =
|
private final JTextField queryField;
|
||||||
new CollectionListModel<MPSite<?>>().selection( this::showSiteResult );
|
private final CollectionListModel<MPSite<?>> sitesModel;
|
||||||
private final JList<MPSite<?>> sitesList =
|
private final JList<MPSite<?>> sitesList;
|
||||||
Components.list( sitesModel, this::getSiteDescription );
|
|
||||||
|
|
||||||
private Future<?> updateSitesJob;
|
private Future<?> updateSitesJob;
|
||||||
|
|
||||||
@ -501,27 +499,32 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
siteToolbar.add( questionsButton );
|
siteToolbar.add( questionsButton );
|
||||||
siteToolbar.add( deleteButton );
|
siteToolbar.add( deleteButton );
|
||||||
settingsButton.setEnabled( false );
|
settingsButton.setEnabled( false );
|
||||||
|
questionsButton.setEnabled( false );
|
||||||
deleteButton.setEnabled( false );
|
deleteButton.setEnabled( false );
|
||||||
|
|
||||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||||
|
|
||||||
add( passwordLabel );
|
add( passwordLabel = Components.label( SwingConstants.CENTER ) );
|
||||||
add( passwordField );
|
add( passwordField = Components.heading( SwingConstants.CENTER ) );
|
||||||
passwordField.setForeground( Res.colors().highlightFg() );
|
passwordField.setForeground( Res.colors().highlightFg() );
|
||||||
passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
|
passwordField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
|
||||||
|
answerField = Components.heading( SwingConstants.CENTER );
|
||||||
|
answerField.setForeground( Res.colors().highlightFg() );
|
||||||
|
answerField.setFont( Res.fonts().bigValueFont( SIZE_RESULT ) );
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
add( Components.strut() );
|
add( Components.strut() );
|
||||||
|
|
||||||
add( queryLabel );
|
add( queryLabel = Components.label() );
|
||||||
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
|
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
|
||||||
add( queryField );
|
add( queryField = Components.textField( null, this::updateSites ) );
|
||||||
queryField.putClientProperty( "JTextField.variant", "search" );
|
queryField.putClientProperty( "JTextField.variant", "search" );
|
||||||
queryField.addActionListener( event -> useSite() );
|
queryField.addActionListener( event -> useSite() );
|
||||||
queryField.addKeyListener( this );
|
queryField.addKeyListener( this );
|
||||||
queryField.requestFocusInWindow();
|
queryField.requestFocusInWindow();
|
||||||
add( Components.strut() );
|
add( Components.strut() );
|
||||||
add( Components.scrollPane( sitesList ) );
|
add( Components.scrollPane( sitesList = Components.list(
|
||||||
sitesModel.registerList( sitesList );
|
sitesModel = new CollectionListModel<MPSite<?>>().selection( this::showSiteResult ),
|
||||||
|
this::getSiteDescription ) ) );
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
|
|
||||||
addHierarchyListener( e -> {
|
addHierarchyListener( e -> {
|
||||||
@ -587,10 +590,56 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
Components.textField( fileSite.getUrl(), fileSite::setUrl ),
|
Components.textField( fileSite.getUrl(), fileSite::setUrl ),
|
||||||
Components.strut() );
|
Components.strut() );
|
||||||
|
|
||||||
Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel(
|
Components.showDialog( this, strf( "Settings for %s", site.getSiteName() ), new JOptionPane( Components.panel(
|
||||||
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
|
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void showSiteQuestions() {
|
||||||
|
MPSite<?> site = sitesModel.getSelectedItem();
|
||||||
|
if (site == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CollectionListModel<MPQuestion> questionsModel = new CollectionListModel<MPQuestion>().selection( this::showQuestionResult );
|
||||||
|
JList<MPQuestion> questionsList = Components.list( questionsModel, MPQuestion::getKeyword );
|
||||||
|
JTextField queryField = Components.textField( null, query -> Res.job( () -> {
|
||||||
|
Collection<MPQuestion> questions = new LinkedList<>( site.findQuestions( query ) );
|
||||||
|
|
||||||
|
if (!Strings.isNullOrEmpty( query ))
|
||||||
|
if (questions.stream().noneMatch( question -> question.getKeyword().equalsIgnoreCase( query ) ))
|
||||||
|
questions.add( new MPNewQuestion( site, query ) );
|
||||||
|
|
||||||
|
Res.ui( () -> questionsModel.set( questions ) );
|
||||||
|
} ) );
|
||||||
|
queryField.putClientProperty( "JTextField.variant", "search" );
|
||||||
|
queryField.addActionListener( event -> useQuestion( questionsModel.getSelectedItem() ) );
|
||||||
|
queryField.addKeyListener( new KeyAdapter() {
|
||||||
|
@Override
|
||||||
|
public void keyPressed(final KeyEvent event) {
|
||||||
|
if ((event.getKeyCode() == KeyEvent.VK_UP) || (event.getKeyCode() == KeyEvent.VK_DOWN))
|
||||||
|
questionsList.dispatchEvent( event );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void keyReleased(final KeyEvent event) {
|
||||||
|
if ((event.getKeyCode() == KeyEvent.VK_UP) || (event.getKeyCode() == KeyEvent.VK_DOWN))
|
||||||
|
questionsList.dispatchEvent( event );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
Components.showDialog( this, strf( "Recovery answers for %s", site.getSiteName() ), new JOptionPane( Components.panel(
|
||||||
|
BoxLayout.PAGE_AXIS,
|
||||||
|
Components.label( "Security Question Keyword:" ), queryField,
|
||||||
|
Components.strut(),
|
||||||
|
Components.label( "Answer:" ), answerField,
|
||||||
|
Components.strut(),
|
||||||
|
Components.scrollPane( questionsList ) ) ) {
|
||||||
|
@Override
|
||||||
|
public void selectInitialValue() {
|
||||||
|
queryField.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteSite() {
|
public void deleteSite() {
|
||||||
MPSite<?> site = sitesModel.getSelectedItem();
|
MPSite<?> site = sitesModel.getSelectedItem();
|
||||||
if (site == null)
|
if (site == null)
|
||||||
@ -668,6 +717,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
passwordLabel.setText( " " );
|
passwordLabel.setText( " " );
|
||||||
passwordField.setText( " " );
|
passwordField.setText( " " );
|
||||||
settingsButton.setEnabled( false );
|
settingsButton.setEnabled( false );
|
||||||
|
questionsButton.setEnabled( false );
|
||||||
deleteButton.setEnabled( false );
|
deleteButton.setEnabled( false );
|
||||||
} );
|
} );
|
||||||
return;
|
return;
|
||||||
@ -683,6 +733,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) );
|
passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) );
|
||||||
passwordField.setText( result );
|
passwordField.setText( result );
|
||||||
settingsButton.setEnabled( true );
|
settingsButton.setEnabled( true );
|
||||||
|
questionsButton.setEnabled( true );
|
||||||
deleteButton.setEnabled( true );
|
deleteButton.setEnabled( true );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
@ -692,6 +743,66 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void useQuestion(@Nullable final MPQuestion question) {
|
||||||
|
if (question instanceof MPNewQuestion) {
|
||||||
|
if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(
|
||||||
|
this,
|
||||||
|
strf( "<html>Remember the answer for the security question with keyword <strong>%s</strong>?</html>",
|
||||||
|
question.getKeyword() ),
|
||||||
|
"New Question", JOptionPane.YES_NO_OPTION )) {
|
||||||
|
useQuestion( question.getSite().addQuestion( question.getKeyword() ) );
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
showQuestionResult( question, result -> {
|
||||||
|
if (result == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (question instanceof MPFileQuestion)
|
||||||
|
((MPFileQuestion) question).use();
|
||||||
|
|
||||||
|
Transferable clipboardContents = new StringSelection( result );
|
||||||
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||||
|
|
||||||
|
Res.ui( () -> {
|
||||||
|
Window answerDialog = SwingUtilities.windowForComponent( answerField );
|
||||||
|
if (answerDialog instanceof Dialog)
|
||||||
|
answerDialog.setVisible( false );
|
||||||
|
|
||||||
|
Window window = SwingUtilities.windowForComponent( UserContentPanel.this );
|
||||||
|
if (window instanceof Frame)
|
||||||
|
((Frame) window).setExtendedState( Frame.ICONIFIED );
|
||||||
|
} );
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showQuestionResult(@Nullable final MPQuestion question) {
|
||||||
|
showQuestionResult( question, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showQuestionResult(@Nullable final MPQuestion question, @Nullable final Consumer<String> resultCallback) {
|
||||||
|
if (question == null) {
|
||||||
|
if (resultCallback != null)
|
||||||
|
resultCallback.accept( null );
|
||||||
|
Res.ui( () -> answerField.setText( " " ) );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Res.job( () -> {
|
||||||
|
try {
|
||||||
|
String answer = question.getAnswer();
|
||||||
|
if (resultCallback != null)
|
||||||
|
resultCallback.accept( answer );
|
||||||
|
|
||||||
|
Res.ui( () -> answerField.setText( answer ) );
|
||||||
|
}
|
||||||
|
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||||
|
logger.err( e, "While resolving answer for: %s", question );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void keyTyped(final KeyEvent event) {
|
public void keyTyped(final KeyEvent event) {
|
||||||
}
|
}
|
||||||
@ -713,13 +824,11 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
|||||||
updateSitesJob.cancel( true );
|
updateSitesJob.cancel( true );
|
||||||
|
|
||||||
updateSitesJob = Res.job( () -> {
|
updateSitesJob = Res.job( () -> {
|
||||||
Collection<MPSite<?>> sites = new LinkedList<>();
|
Collection<MPSite<?>> sites = new LinkedList<>( user.findSites( query ) );
|
||||||
if (!Strings.isNullOrEmpty( query )) {
|
|
||||||
sites.addAll( new LinkedList<>( user.findSites( query ) ) );
|
|
||||||
|
|
||||||
|
if (!Strings.isNullOrEmpty( query ))
|
||||||
if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
|
if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
|
||||||
sites.add( new MPNewSite( user, query ) );
|
sites.add( new MPNewSite( user, query ) );
|
||||||
}
|
|
||||||
|
|
||||||
Res.ui( () -> sitesModel.set( sites ) );
|
Res.ui( () -> sitesModel.set( sites ) );
|
||||||
} );
|
} );
|
||||||
|
@ -40,6 +40,13 @@ public interface MPQuestion extends Comparable<MPQuestion> {
|
|||||||
|
|
||||||
void setType(MPResultType type);
|
void setType(MPResultType type);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
default String getAnswer()
|
||||||
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
|
return getAnswer( null );
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
String getAnswer(@Nullable String state)
|
String getAnswer(@Nullable String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableCollection;
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
@ -57,6 +58,7 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
|||||||
|
|
||||||
void setLoginType(@Nullable MPResultType loginType);
|
void setLoginType(@Nullable MPResultType loginType);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
default String getResult()
|
default String getResult()
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
@ -79,6 +81,16 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
|||||||
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
|
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext, @Nullable String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
String getResult(MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||||
|
@Nullable UnsignedInteger counter, MPResultType type, @Nullable String state)
|
||||||
|
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
String getState(MPKeyPurpose keyPurpose, @Nullable String keyContext,
|
||||||
|
@Nullable UnsignedInteger counter, MPResultType type, String state)
|
||||||
|
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
default String getLogin()
|
default String getLogin()
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
@ -94,10 +106,17 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
MPUser<?> getUser();
|
MPUser<?> getUser();
|
||||||
|
|
||||||
boolean addQuestion(Q question);
|
@Nonnull
|
||||||
|
Q addQuestion(String keyword);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
Q addQuestion(Q question);
|
||||||
|
|
||||||
boolean deleteQuestion(Q question);
|
boolean deleteQuestion(Q question);
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Collection<Q> getQuestions();
|
Collection<Q> getQuestions();
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
ImmutableCollection<Q> findQuestions(@Nullable String query);
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
|||||||
|
|
||||||
// - Relations
|
// - Relations
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
S addSite(String siteName);
|
S addSite(String siteName);
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -22,6 +22,7 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
|||||||
|
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.model.MPQuestion;
|
import com.lyndir.masterpassword.model.MPQuestion;
|
||||||
|
import com.lyndir.masterpassword.model.MPSite;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -33,14 +34,23 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
*/
|
*/
|
||||||
public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||||
|
|
||||||
|
private final MPSite<?> site;
|
||||||
private final String keyword;
|
private final String keyword;
|
||||||
private MPResultType type;
|
|
||||||
|
|
||||||
protected MPBasicQuestion(final String keyword, final MPResultType type) {
|
private MPResultType type;
|
||||||
|
|
||||||
|
protected MPBasicQuestion(final MPSite<?> site, final String keyword, final MPResultType type) {
|
||||||
|
this.site = site;
|
||||||
this.keyword = keyword;
|
this.keyword = keyword;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public MPSite<?> getSite() {
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getKeyword() {
|
public String getKeyword() {
|
||||||
@ -55,7 +65,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setType(final MPResultType type) {
|
public void setType(final MPResultType type) {
|
||||||
if (Objects.equals(this.type, type))
|
if (this.type == type)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@ -70,15 +80,12 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
|||||||
return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state );
|
return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public abstract MPBasicSite<?, ?> getSite();
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onChanged() {
|
protected void onChanged() {
|
||||||
super.onChanged();
|
super.onChanged();
|
||||||
|
|
||||||
getSite().setChanged();
|
if (site instanceof Changeable)
|
||||||
|
((Changeable) site).setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -21,6 +21,9 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableCollection;
|
||||||
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.model.*;
|
import com.lyndir.masterpassword.model.*;
|
||||||
@ -35,9 +38,9 @@ import javax.annotation.Nullable;
|
|||||||
public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> extends Changeable
|
public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> extends Changeable
|
||||||
implements MPSite<Q> {
|
implements MPSite<Q> {
|
||||||
|
|
||||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
|
||||||
private final U user;
|
private final U user;
|
||||||
private final String siteName;
|
private final String siteName;
|
||||||
|
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||||
|
|
||||||
private MPAlgorithm algorithm;
|
private MPAlgorithm algorithm;
|
||||||
private UnsignedInteger counter;
|
private UnsignedInteger counter;
|
||||||
@ -104,7 +107,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setResultType(final MPResultType resultType) {
|
public void setResultType(final MPResultType resultType) {
|
||||||
if (Objects.equals( this.resultType, resultType ))
|
if (this.resultType == resultType)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.resultType = resultType;
|
this.resultType = resultType;
|
||||||
@ -119,7 +122,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setLoginType(@Nullable final MPResultType loginType) {
|
public void setLoginType(@Nullable final MPResultType loginType) {
|
||||||
if (Objects.equals( this.loginType, loginType ))
|
if (this.loginType == loginType)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
||||||
@ -134,8 +137,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
|||||||
return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state );
|
return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
@Nonnull
|
||||||
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state)
|
@Override
|
||||||
|
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||||
|
@Nullable final UnsignedInteger counter, final MPResultType type, @Nullable final String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
return getUser().getMasterKey().siteResult(
|
return getUser().getMasterKey().siteResult(
|
||||||
@ -143,8 +148,10 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
|||||||
keyPurpose, keyContext, type, state );
|
keyPurpose, keyContext, type, state );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
@Nonnull
|
||||||
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
|
@Override
|
||||||
|
public String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||||
|
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
return getUser().getMasterKey().siteState(
|
return getUser().getMasterKey().siteState(
|
||||||
@ -160,13 +167,13 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
|||||||
return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state );
|
return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public boolean addQuestion(final Q question) {
|
public Q addQuestion(final Q question) {
|
||||||
if (!questions.add( question ))
|
questions.add( question );
|
||||||
return false;
|
|
||||||
|
|
||||||
setChanged();
|
setChanged();
|
||||||
return true;
|
return question;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -184,6 +191,17 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
|||||||
return Collections.unmodifiableCollection( questions );
|
return Collections.unmodifiableCollection( questions );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public ImmutableCollection<Q> findQuestions(@Nullable final String query) {
|
||||||
|
ImmutableSortedSet.Builder<Q> results = ImmutableSortedSet.naturalOrder();
|
||||||
|
for (final Q question : getQuestions())
|
||||||
|
if (Strings.isNullOrEmpty( query ) || question.getKeyword().startsWith( query ))
|
||||||
|
results.add( question );
|
||||||
|
|
||||||
|
return results.build();
|
||||||
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public U getUser() {
|
public U getUser() {
|
||||||
|
@ -20,6 +20,7 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableCollection;
|
import com.google.common.collect.ImmutableCollection;
|
||||||
import com.google.common.collect.ImmutableSortedSet;
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
@ -202,10 +203,9 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
@Override
|
@Override
|
||||||
public ImmutableCollection<S> findSites(@Nullable final String query) {
|
public ImmutableCollection<S> findSites(@Nullable final String query) {
|
||||||
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
|
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
|
||||||
if (query != null)
|
for (final S site : getSites())
|
||||||
for (final S site : getSites())
|
if (Strings.isNullOrEmpty( query ) || site.getSiteName().startsWith( query ))
|
||||||
if (site.getSiteName().startsWith( query ))
|
results.add( site );
|
||||||
results.add( site );
|
|
||||||
|
|
||||||
return results.build();
|
return results.build();
|
||||||
}
|
}
|
||||||
|
@ -30,16 +30,13 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public class MPFileQuestion extends MPBasicQuestion {
|
public class MPFileQuestion extends MPBasicQuestion {
|
||||||
|
|
||||||
private final MPFileSite site;
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String answerState;
|
private String answerState;
|
||||||
|
|
||||||
public MPFileQuestion(final MPFileSite site, final String keyword,
|
public MPFileQuestion(final MPFileSite site, final String keyword,
|
||||||
@Nullable final MPResultType type, @Nullable final String answerState) {
|
@Nullable final MPResultType type, @Nullable final String answerState) {
|
||||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||||
|
|
||||||
this.site = site;
|
|
||||||
this.answerState = answerState;
|
this.answerState = answerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +45,8 @@ public class MPFileQuestion extends MPBasicQuestion {
|
|||||||
return answerState;
|
return answerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
public String getAnswer()
|
public String getAnswer()
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
return getAnswer( answerState );
|
return getAnswer( answerState );
|
||||||
@ -66,9 +65,8 @@ public class MPFileQuestion extends MPBasicQuestion {
|
|||||||
setChanged();
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
public void use() {
|
||||||
@Override
|
if (getSite() instanceof MPFileSite)
|
||||||
public MPFileSite getSite() {
|
((MPFileSite) getSite()).use();
|
||||||
return site;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,6 +145,12 @@ public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
|
|||||||
setChanged();
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public MPFileQuestion addQuestion(final String keyword) {
|
||||||
|
return addQuestion( new MPFileQuestion( this, keyword, null, null ) );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(@Nonnull final MPSite<?> o) {
|
public int compareTo(@Nonnull final MPSite<?> o) {
|
||||||
int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0;
|
int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0;
|
||||||
|
@ -207,6 +207,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
super.reset();
|
super.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPFileSite addSite(final String siteName) {
|
public MPFileSite addSite(final String siteName) {
|
||||||
return addSite( new MPFileSite( this, siteName ) );
|
return addSite( new MPFileSite( this, siteName ) );
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit b190b45756666da71fa8037501530b14c609be24
|
Subproject commit 0623231ad5614d5e6541199fd755589f04e214cb
|
Loading…
Reference in New Issue
Block a user