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 -a tools <<< "${tools:-$pkg}"
|
||||
for tool in "${tools[@]}"; do
|
||||
hash "$tool" && continue 2
|
||||
hash "$tool" 2>/dev/null && continue 2
|
||||
done
|
||||
|
||||
echo >&2 "Missing: $pkg. Please install this package."
|
||||
|
@ -31,17 +31,7 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class MPIncognitoQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPIncognitoSite site;
|
||||
|
||||
public MPIncognitoQuestion(final MPIncognitoSite site, final String keyword, @Nullable final MPResultType type) {
|
||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
|
||||
this.site = site;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoSite getSite() {
|
||||
return site;
|
||||
super( site, keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||
}
|
||||
}
|
||||
|
@ -40,4 +40,10 @@ public class MPIncognitoSite extends MPBasicSite<MPIncognitoUser, MPIncognitoQue
|
||||
@Nullable final MPResultType resultType, @Nullable final MPResultType 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.model.impl.MPBasicUser;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
@ -38,6 +39,7 @@ public class MPIncognitoUser extends MPBasicUser<MPIncognitoSite> {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPIncognitoSite addSite(final String 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.impl.*;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
@ -12,4 +13,10 @@ public class MPNewSite extends MPBasicSite<MPUser<?>, MPQuestion> {
|
||||
public MPNewSite(final MPUser<?> user, final String 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) {
|
||||
JDialog dialog = pane.createDialog( owner, title );
|
||||
dialog.setMinimumSize( new Dimension( 520, 0 ) );
|
||||
dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL );
|
||||
showDialog( dialog );
|
||||
|
||||
@ -143,7 +144,6 @@ public abstract class Components {
|
||||
title, Dialog.ModalityType.DOCUMENT_MODAL );
|
||||
dialog.setMinimumSize( new Dimension( 320, 0 ) );
|
||||
dialog.setLocationRelativeTo( owner );
|
||||
dialog.setLocationByPlatform( true );
|
||||
dialog.setContentPane( content );
|
||||
|
||||
return showDialog( dialog );
|
||||
@ -155,6 +155,7 @@ public abstract class Components {
|
||||
dialog.getRootPane().putClientProperty( "Window.style", "small" );
|
||||
dialog.pack();
|
||||
|
||||
dialog.setLocationByPlatform( true );
|
||||
dialog.setVisible( true );
|
||||
|
||||
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 ) {
|
||||
{
|
||||
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 ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
if (selection != null)
|
||||
if (change != null) {
|
||||
getDocument().addDocumentListener( new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
selection.accept( getText() );
|
||||
change.accept( getText() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
selection.accept( getText() );
|
||||
change.accept( getText() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
selection.accept( getText() );
|
||||
change.accept( getText() );
|
||||
}
|
||||
} );
|
||||
change.accept( getText() );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -228,6 +231,7 @@ public abstract class Components {
|
||||
public static <E> JList<E> list(final ListModel<E> model, final Function<E, String> valueTransformer) {
|
||||
return new JList<E>( model ) {
|
||||
{
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
||||
setCellRenderer( new DefaultListCellRenderer() {
|
||||
@Override
|
||||
@ -241,7 +245,9 @@ public abstract class Components {
|
||||
return this;
|
||||
}
|
||||
} );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
if (model instanceof CollectionListModel)
|
||||
((CollectionListModel<E>) model).registerList( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -9,8 +9,6 @@ import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||
import java.awt.*;
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
|
||||
@ -26,8 +24,6 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
||||
|
||||
private final CollectionListModel<MPUser<?>> usersModel =
|
||||
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() {
|
||||
setOpaque( false );
|
||||
@ -46,7 +42,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
||||
add( Components.strut( Components.margin() ) );
|
||||
|
||||
// User Selection
|
||||
add( userField );
|
||||
add( Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) ) );
|
||||
|
||||
MPFileUserManager.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.masterpassword.gui.util.Components;
|
||||
import com.lyndir.masterpassword.gui.util.Res;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ComponentAdapter;
|
||||
import java.awt.event.ComponentEvent;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.BevelBorder;
|
||||
|
||||
@ -23,8 +21,6 @@ public class MasterPasswordFrame extends JFrame {
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
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();
|
||||
|
||||
@SuppressWarnings("MagicNumber")
|
||||
@ -32,15 +28,16 @@ public class MasterPasswordFrame extends JFrame {
|
||||
super( "Master Password" );
|
||||
|
||||
setContentPane( root );
|
||||
root.add( filesPanel );
|
||||
root.add( new FilesPanel() );
|
||||
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.getSiteToolbar(), BorderLayout.LINE_END );
|
||||
userPanel.add( Components.borderPanel(
|
||||
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
|
||||
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
|
||||
root.add( userPanel );
|
||||
|
||||
addComponentListener( new ComponentHandler() );
|
||||
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.gui.MPGuiConstants;
|
||||
import com.lyndir.masterpassword.gui.MasterPassword;
|
||||
import com.lyndir.masterpassword.gui.model.MPIncognitoUser;
|
||||
import com.lyndir.masterpassword.gui.model.MPNewSite;
|
||||
import com.lyndir.masterpassword.gui.model.*;
|
||||
import com.lyndir.masterpassword.gui.util.*;
|
||||
import com.lyndir.masterpassword.gui.util.Platform;
|
||||
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(),
|
||||
"Change the master password for this user." );
|
||||
|
||||
private final JPasswordField masterPasswordField = Components.passwordField();
|
||||
private final JLabel errorLabel = Components.label();
|
||||
private final JLabel identiconLabel = Components.label( SwingConstants.CENTER );
|
||||
private final JPasswordField masterPasswordField;
|
||||
private final JLabel errorLabel;
|
||||
private final JLabel identiconLabel;
|
||||
|
||||
private Future<?> identiconJob;
|
||||
|
||||
@ -312,16 +311,16 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||
add( Components.strut() );
|
||||
|
||||
add( identiconLabel );
|
||||
add( identiconLabel = Components.label( SwingConstants.CENTER ) );
|
||||
identiconLabel.setFont( Res.fonts().emoticonsFont( Components.TEXT_SIZE_CONTROL ) );
|
||||
add( Box.createGlue() );
|
||||
|
||||
add( Components.label( "Master Password:" ) );
|
||||
add( Components.strut() );
|
||||
add( masterPasswordField );
|
||||
add( masterPasswordField = Components.passwordField() );
|
||||
masterPasswordField.addActionListener( this );
|
||||
masterPasswordField.getDocument().addDocumentListener( this );
|
||||
add( errorLabel );
|
||||
add( errorLabel = Components.label() );
|
||||
errorLabel.setForeground( Res.colors().errorFg() );
|
||||
add( Box.createGlue() );
|
||||
}
|
||||
@ -468,21 +467,20 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
"Sign out and lock user." );
|
||||
private final JButton settingsButton = Components.button( Res.icons().settings(), event -> showSiteSettings(),
|
||||
"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." );
|
||||
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(),
|
||||
"Delete the site from the user." );
|
||||
|
||||
@Nonnull
|
||||
private final MPUser<?> user;
|
||||
private final JLabel passwordLabel = Components.label( SwingConstants.CENTER );
|
||||
private final JLabel passwordField = Components.heading( SwingConstants.CENTER );
|
||||
private final JLabel queryLabel = Components.label();
|
||||
private final JTextField queryField = Components.textField( null, this::updateSites );
|
||||
private final CollectionListModel<MPSite<?>> sitesModel =
|
||||
new CollectionListModel<MPSite<?>>().selection( this::showSiteResult );
|
||||
private final JList<MPSite<?>> sitesList =
|
||||
Components.list( sitesModel, this::getSiteDescription );
|
||||
private final JLabel passwordLabel;
|
||||
private final JLabel passwordField;
|
||||
private final JLabel answerField;
|
||||
private final JLabel queryLabel;
|
||||
private final JTextField queryField;
|
||||
private final CollectionListModel<MPSite<?>> sitesModel;
|
||||
private final JList<MPSite<?>> sitesList;
|
||||
|
||||
private Future<?> updateSitesJob;
|
||||
|
||||
@ -501,27 +499,32 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
siteToolbar.add( questionsButton );
|
||||
siteToolbar.add( deleteButton );
|
||||
settingsButton.setEnabled( false );
|
||||
questionsButton.setEnabled( false );
|
||||
deleteButton.setEnabled( false );
|
||||
|
||||
add( Components.heading( user.getFullName(), SwingConstants.CENTER ) );
|
||||
|
||||
add( passwordLabel );
|
||||
add( passwordField );
|
||||
add( passwordLabel = Components.label( SwingConstants.CENTER ) );
|
||||
add( passwordField = Components.heading( SwingConstants.CENTER ) );
|
||||
passwordField.setForeground( Res.colors().highlightFg() );
|
||||
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( Components.strut() );
|
||||
|
||||
add( queryLabel );
|
||||
add( queryLabel = Components.label() );
|
||||
queryLabel.setText( strf( "%s's password for:", user.getFullName() ) );
|
||||
add( queryField );
|
||||
add( queryField = Components.textField( null, this::updateSites ) );
|
||||
queryField.putClientProperty( "JTextField.variant", "search" );
|
||||
queryField.addActionListener( event -> useSite() );
|
||||
queryField.addKeyListener( this );
|
||||
queryField.requestFocusInWindow();
|
||||
add( Components.strut() );
|
||||
add( Components.scrollPane( sitesList ) );
|
||||
sitesModel.registerList( sitesList );
|
||||
add( Components.scrollPane( sitesList = Components.list(
|
||||
sitesModel = new CollectionListModel<MPSite<?>>().selection( this::showSiteResult ),
|
||||
this::getSiteDescription ) ) );
|
||||
add( Box.createGlue() );
|
||||
|
||||
addHierarchyListener( e -> {
|
||||
@ -587,10 +590,56 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
Components.textField( fileSite.getUrl(), fileSite::setUrl ),
|
||||
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] ) ) ) );
|
||||
}
|
||||
|
||||
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() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
if (site == null)
|
||||
@ -668,6 +717,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
passwordLabel.setText( " " );
|
||||
passwordField.setText( " " );
|
||||
settingsButton.setEnabled( false );
|
||||
questionsButton.setEnabled( false );
|
||||
deleteButton.setEnabled( false );
|
||||
} );
|
||||
return;
|
||||
@ -683,6 +733,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
passwordLabel.setText( strf( "Your password for %s:", site.getSiteName() ) );
|
||||
passwordField.setText( result );
|
||||
settingsButton.setEnabled( true );
|
||||
questionsButton.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
|
||||
public void keyTyped(final KeyEvent event) {
|
||||
}
|
||||
@ -713,13 +824,11 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
updateSitesJob.cancel( true );
|
||||
|
||||
updateSitesJob = Res.job( () -> {
|
||||
Collection<MPSite<?>> sites = new LinkedList<>();
|
||||
if (!Strings.isNullOrEmpty( query )) {
|
||||
sites.addAll( new LinkedList<>( user.findSites( query ) ) );
|
||||
Collection<MPSite<?>> sites = new LinkedList<>( user.findSites( query ) );
|
||||
|
||||
if (!Strings.isNullOrEmpty( query ))
|
||||
if (sites.stream().noneMatch( site -> site.getSiteName().equalsIgnoreCase( query ) ))
|
||||
sites.add( new MPNewSite( user, query ) );
|
||||
}
|
||||
|
||||
Res.ui( () -> sitesModel.set( sites ) );
|
||||
} );
|
||||
|
@ -40,6 +40,13 @@ public interface MPQuestion extends Comparable<MPQuestion> {
|
||||
|
||||
void setType(MPResultType type);
|
||||
|
||||
@Nonnull
|
||||
default String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
return getAnswer( null );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
String getAnswer(@Nullable String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.google.common.collect.ImmutableCollection;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.Collection;
|
||||
@ -57,6 +58,7 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
||||
|
||||
void setLoginType(@Nullable MPResultType loginType);
|
||||
|
||||
@Nonnull
|
||||
default String getResult()
|
||||
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)
|
||||
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
|
||||
default String getLogin()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
@ -94,10 +106,17 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
||||
@Nonnull
|
||||
MPUser<?> getUser();
|
||||
|
||||
boolean addQuestion(Q question);
|
||||
@Nonnull
|
||||
Q addQuestion(String keyword);
|
||||
|
||||
@Nonnull
|
||||
Q addQuestion(Q question);
|
||||
|
||||
boolean deleteQuestion(Q question);
|
||||
|
||||
@Nonnull
|
||||
Collection<Q> getQuestions();
|
||||
|
||||
@Nonnull
|
||||
ImmutableCollection<Q> findQuestions(@Nullable String query);
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
|
||||
// - Relations
|
||||
|
||||
@Nonnull
|
||||
S addSite(String siteName);
|
||||
|
||||
@Nonnull
|
||||
|
@ -22,6 +22,7 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPQuestion;
|
||||
import com.lyndir.masterpassword.model.MPSite;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@ -33,14 +34,23 @@ import org.jetbrains.annotations.NotNull;
|
||||
*/
|
||||
public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
|
||||
private final MPSite<?> site;
|
||||
private final String keyword;
|
||||
|
||||
private MPResultType type;
|
||||
|
||||
protected MPBasicQuestion(final String keyword, final MPResultType type) {
|
||||
protected MPBasicQuestion(final MPSite<?> site, final String keyword, final MPResultType type) {
|
||||
this.site = site;
|
||||
this.keyword = keyword;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPSite<?> getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getKeyword() {
|
||||
@ -55,7 +65,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
|
||||
@Override
|
||||
public void setType(final MPResultType type) {
|
||||
if (Objects.equals(this.type, type))
|
||||
if (this.type == type)
|
||||
return;
|
||||
|
||||
this.type = type;
|
||||
@ -70,15 +80,12 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||
return getSite().getResult( MPKeyPurpose.Recovery, getKeyword(), null, getType(), state );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public abstract MPBasicSite<?, ?> getSite();
|
||||
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
getSite().setChanged();
|
||||
if (site instanceof Changeable)
|
||||
((Changeable) site).setChanged();
|
||||
}
|
||||
|
||||
@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.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.lyndir.masterpassword.*;
|
||||
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
|
||||
implements MPSite<Q> {
|
||||
|
||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||
private final U user;
|
||||
private final String siteName;
|
||||
private final Collection<Q> questions = new LinkedHashSet<>();
|
||||
|
||||
private MPAlgorithm algorithm;
|
||||
private UnsignedInteger counter;
|
||||
@ -104,7 +107,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
|
||||
@Override
|
||||
public void setResultType(final MPResultType resultType) {
|
||||
if (Objects.equals( this.resultType, resultType ))
|
||||
if (this.resultType == resultType)
|
||||
return;
|
||||
|
||||
this.resultType = resultType;
|
||||
@ -119,7 +122,7 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
|
||||
@Override
|
||||
public void setLoginType(@Nullable final MPResultType loginType) {
|
||||
if (Objects.equals( this.loginType, loginType ))
|
||||
if (this.loginType == loginType)
|
||||
return;
|
||||
|
||||
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
||||
@ -134,7 +137,9 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
return getResult( keyPurpose, keyContext, getCounter(), getResultType(), state );
|
||||
}
|
||||
|
||||
protected String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nonnull
|
||||
@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 {
|
||||
|
||||
@ -143,7 +148,9 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
keyPurpose, keyContext, type, state );
|
||||
}
|
||||
|
||||
protected String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getState(final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
|
||||
@Nullable final UnsignedInteger counter, final MPResultType type, final String state)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
|
||||
@ -160,13 +167,13 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
return getResult( MPKeyPurpose.Identification, null, null, getLoginType(), state );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public boolean addQuestion(final Q question) {
|
||||
if (!questions.add( question ))
|
||||
return false;
|
||||
public Q addQuestion(final Q question) {
|
||||
questions.add( question );
|
||||
|
||||
setChanged();
|
||||
return true;
|
||||
return question;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -184,6 +191,17 @@ public abstract class MPBasicSite<U extends MPUser<?>, Q extends MPQuestion> ext
|
||||
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
|
||||
@Override
|
||||
public U getUser() {
|
||||
|
@ -20,6 +20,7 @@ package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
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.lyndir.lhunath.opal.system.CodeUtils;
|
||||
@ -202,9 +203,8 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
||||
@Override
|
||||
public ImmutableCollection<S> findSites(@Nullable final String query) {
|
||||
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
|
||||
if (query != null)
|
||||
for (final S site : getSites())
|
||||
if (site.getSiteName().startsWith( query ))
|
||||
if (Strings.isNullOrEmpty( query ) || site.getSiteName().startsWith( query ))
|
||||
results.add( site );
|
||||
|
||||
return results.build();
|
||||
|
@ -30,16 +30,13 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
public class MPFileQuestion extends MPBasicQuestion {
|
||||
|
||||
private final MPFileSite site;
|
||||
|
||||
@Nullable
|
||||
private String answerState;
|
||||
|
||||
public MPFileQuestion(final MPFileSite site, final String keyword,
|
||||
@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;
|
||||
}
|
||||
|
||||
@ -48,6 +45,8 @@ public class MPFileQuestion extends MPBasicQuestion {
|
||||
return answerState;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String getAnswer()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
return getAnswer( answerState );
|
||||
@ -66,9 +65,8 @@ public class MPFileQuestion extends MPBasicQuestion {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileSite getSite() {
|
||||
return site;
|
||||
public void use() {
|
||||
if (getSite() instanceof MPFileSite)
|
||||
((MPFileSite) getSite()).use();
|
||||
}
|
||||
}
|
||||
|
@ -145,6 +145,12 @@ public class MPFileSite extends MPBasicSite<MPFileUser, MPFileQuestion> {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileQuestion addQuestion(final String keyword) {
|
||||
return addQuestion( new MPFileQuestion( this, keyword, null, null ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(@Nonnull final MPSite<?> o) {
|
||||
int comparison = (o instanceof MPFileSite)? ((MPFileSite) o).getLastUsed().compareTo( getLastUsed() ): 0;
|
||||
|
@ -207,6 +207,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
super.reset();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileSite addSite(final String siteName) {
|
||||
return addSite( new MPFileSite( this, siteName ) );
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit b190b45756666da71fa8037501530b14c609be24
|
||||
Subproject commit 0623231ad5614d5e6541199fd755589f04e214cb
|
Loading…
Reference in New Issue
Block a user