Key calculator and access to the full algorithm.
This commit is contained in:
parent
9a40e52d53
commit
d5551c8c8c
@ -24,6 +24,8 @@ shadowJar {
|
||||
attributes 'Implementation-Version': version
|
||||
}
|
||||
doLast {
|
||||
println("doLast: ${System.env.KEY_PW_DESKTOP}");
|
||||
println("doLast: "+System.getenv( 'KEY_PW_DESKTOP' ));
|
||||
if (System.getenv( 'KEY_PW_DESKTOP' ) != null)
|
||||
ant.signjar( jar: archivePath,
|
||||
alias: 'masterpassword-desktop',
|
||||
|
@ -1,6 +1,5 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
@ -18,25 +17,21 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
|
||||
private final List<E> model = new LinkedList<>();
|
||||
@Nullable
|
||||
private E selectedItem;
|
||||
private JList<E> list;
|
||||
@Nullable
|
||||
private E selectedItem;
|
||||
@Nullable
|
||||
private Consumer<E> selectionConsumer;
|
||||
|
||||
@SafeVarargs
|
||||
public static <E> CollectionListModel<E> copy(final E... elements) {
|
||||
return copy( Arrays.asList( elements ) );
|
||||
public CollectionListModel(final E... elements) {
|
||||
this( Arrays.asList( elements ) );
|
||||
}
|
||||
|
||||
public static <E> CollectionListModel<E> copy(final Collection<? extends E> elements) {
|
||||
CollectionListModel<E> model = new CollectionListModel<>();
|
||||
synchronized (model) {
|
||||
model.model.addAll( elements );
|
||||
model.selectedItem = model.getElementAt( 0 );
|
||||
model.fireIntervalAdded( model, 0, model.model.size() );
|
||||
|
||||
return model;
|
||||
}
|
||||
public CollectionListModel(final Collection<? extends E> elements) {
|
||||
model.addAll( elements );
|
||||
selectedItem = getElementAt( 0 );
|
||||
fireIntervalAdded( this, 0, model.size() );
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -44,8 +39,8 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
return model.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
@Override
|
||||
public synchronized E getElementAt(final int index) {
|
||||
return (index < model.size())? model.get( index ): null;
|
||||
}
|
||||
@ -109,6 +104,11 @@ public class CollectionListModel<E> extends AbstractListModel<E>
|
||||
return selectedItem;
|
||||
}
|
||||
|
||||
public CollectionListModel<E> select(final E selectedItem) {
|
||||
setSelectedItem( selectedItem );
|
||||
return this;
|
||||
}
|
||||
|
||||
public synchronized void registerList(final JList<E> list) {
|
||||
// TODO: This class should probably implement ListSelectionModel instead.
|
||||
if (this.list != null)
|
||||
|
@ -20,7 +20,6 @@ package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.util.Utilities;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
@ -34,8 +33,8 @@ import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.Border;
|
||||
import javax.swing.border.CompoundBorder;
|
||||
import javax.swing.event.*;
|
||||
import javax.swing.text.DefaultFormatterFactory;
|
||||
import javax.swing.event.HyperlinkEvent;
|
||||
import javax.swing.text.*;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
|
||||
@ -114,9 +113,14 @@ public abstract class Components {
|
||||
if (options == null)
|
||||
return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION;
|
||||
|
||||
try {
|
||||
int option = Arrays.binarySearch( options, selectedValue );
|
||||
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
|
||||
}
|
||||
catch (final ClassCastException ignored) {
|
||||
return JOptionPane.CLOSED_OPTION;
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static File showLoadDialog(@Nullable final Component owner, final String title) {
|
||||
@ -164,7 +168,11 @@ public abstract class Components {
|
||||
}
|
||||
|
||||
public static JTextField textField() {
|
||||
return new JTextField() {
|
||||
return textField( null );
|
||||
}
|
||||
|
||||
public static JTextField textField(@Nullable final Document document) {
|
||||
return new JTextField( document, null, 0 ) {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
@ -174,43 +182,30 @@ public abstract class Components {
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static JTextField textField(@Nullable final String text, @Nullable final Consumer<String> change) {
|
||||
return new JTextField( text ) {
|
||||
return textField( new DocumentModel( new PlainDocument() ).selection( text, change ).getDocument() );
|
||||
}
|
||||
|
||||
public static JTextArea textArea() {
|
||||
return new JTextArea() {
|
||||
{
|
||||
setBorder( BorderFactory.createCompoundBorder( BorderFactory.createLineBorder( Res.colors().controlBorder(), 1, true ),
|
||||
BorderFactory.createEmptyBorder( 4, 4, 4, 4 ) ) );
|
||||
setFont( Res.fonts().valueFont( TEXT_SIZE_CONTROL ) );
|
||||
setAlignmentX( LEFT_ALIGNMENT );
|
||||
|
||||
if (change != null) {
|
||||
getDocument().addDocumentListener( new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
change.accept( getText() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
change.accept( getText() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
change.accept( getText() );
|
||||
}
|
||||
} );
|
||||
change.accept( getText() );
|
||||
}
|
||||
setLineWrap( true );
|
||||
setRows( 3 );
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dimension getMaximumSize() {
|
||||
return new Dimension( Integer.MAX_VALUE, getPreferredSize().height );
|
||||
return new Dimension( Integer.MAX_VALUE, Integer.MAX_VALUE );
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -443,17 +438,17 @@ public abstract class Components {
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final E[] values, final Function<E, String> valueTransformer,
|
||||
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
|
||||
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
}
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
|
||||
@Nullable final Consumer<E> selectionConsumer) {
|
||||
return comboBox( CollectionListModel.copy( values ).selection( selectionConsumer ), valueTransformer );
|
||||
return comboBox( new CollectionListModel<>( values ).selection( selectionConsumer ), valueTransformer );
|
||||
}
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final Collection<E> values, final Function<E, String> valueTransformer,
|
||||
@Nullable final E selectedItem, @Nullable final Consumer<E> selectionConsumer) {
|
||||
return comboBox( CollectionListModel.copy( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
return comboBox( new CollectionListModel<>( values ).selection( selectedItem, selectionConsumer ), valueTransformer );
|
||||
}
|
||||
|
||||
public static <E> JComboBox<E> comboBox(final ComboBoxModel<E> model, final Function<E, String> valueTransformer) {
|
||||
|
@ -0,0 +1,27 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-08-23
|
||||
*/
|
||||
public class ConsumingTrigger<T> implements Consumer<T> {
|
||||
|
||||
private final Runnable trigger;
|
||||
|
||||
@Nullable
|
||||
private T value;
|
||||
|
||||
public ConsumingTrigger(final Runnable trigger) {
|
||||
this.trigger = trigger;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(final T t) {
|
||||
value = t;
|
||||
|
||||
trigger.run();
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package com.lyndir.masterpassword.gui.util;
|
||||
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.function.Consumer;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2018-08-24
|
||||
*/
|
||||
public class DocumentModel implements Selectable<String, DocumentModel> {
|
||||
|
||||
private static final Logger logger = Logger.get( DocumentModel.class );
|
||||
|
||||
private final Document document;
|
||||
|
||||
@Nullable
|
||||
private DocumentListener documentListener;
|
||||
|
||||
public DocumentModel(final Document document) {
|
||||
this.document = document;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public Document getDocument() {
|
||||
return document;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getText() {
|
||||
try {
|
||||
return (document.getLength() > 0)? document.getText( 0, document.getLength() ): null;
|
||||
}
|
||||
catch (final BadLocationException e) {
|
||||
logger.wrn( "While getting text for model", e );
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setText(@Nullable final String text) {
|
||||
try {
|
||||
if (document.getLength() > 0)
|
||||
document.remove( 0, document.getLength() );
|
||||
|
||||
if (text != null)
|
||||
document.insertString( 0, text, null );
|
||||
}
|
||||
catch (final BadLocationException e) {
|
||||
logger.err( "While setting text for model", e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentModel selection(@Nullable final Consumer<String> selectionConsumer) {
|
||||
if (documentListener != null)
|
||||
document.removeDocumentListener( documentListener );
|
||||
|
||||
if (selectionConsumer != null)
|
||||
document.addDocumentListener( documentListener = new DocumentListener() {
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
trigger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
trigger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
trigger();
|
||||
}
|
||||
|
||||
private void trigger() {
|
||||
selectionConsumer.accept( getText() );
|
||||
}
|
||||
} );
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DocumentModel selection(@Nullable final String selectedItem, @Nullable final Consumer<String> selectionConsumer) {
|
||||
selection( selectionConsumer );
|
||||
setText( selectedItem );
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
@ -164,6 +164,10 @@ public abstract class Res {
|
||||
return icon( "media/icon_edit.png" );
|
||||
}
|
||||
|
||||
public Icon key() {
|
||||
return icon( "media/icon_key.png" );
|
||||
}
|
||||
|
||||
public Icon avatar(final int index) {
|
||||
return icon( strf( "media/avatar-%d.png", index % avatars() ) );
|
||||
}
|
||||
|
@ -11,5 +11,5 @@ public interface Selectable<E, T> {
|
||||
|
||||
T selection(@Nullable Consumer<E> selectionConsumer);
|
||||
|
||||
T selection(E selectedItem, @Nullable Consumer<E> selectionConsumer);
|
||||
T selection(@Nullable E selectedItem, @Nullable Consumer<E> selectionConsumer);
|
||||
}
|
||||
|
@ -109,13 +109,14 @@ public class UnsignedIntegerModel extends SpinnerNumberModel implements Selectab
|
||||
}
|
||||
|
||||
@Override
|
||||
public UnsignedIntegerModel selection(final UnsignedInteger selectedItem, @Nullable final Consumer<UnsignedInteger> selectionConsumer) {
|
||||
public UnsignedIntegerModel selection(@Nullable final UnsignedInteger selectedItem,
|
||||
@Nullable final Consumer<UnsignedInteger> selectionConsumer) {
|
||||
if (changeListener != null) {
|
||||
removeChangeListener( changeListener );
|
||||
changeListener = null;
|
||||
}
|
||||
|
||||
setValue( selectedItem );
|
||||
setValue( (selectedItem != null)? selectedItem: getMinimum() );
|
||||
return selection( selectionConsumer );
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener, Ma
|
||||
"Click to change the user's avatar." );
|
||||
|
||||
private final CollectionListModel<MPUser<?>> usersModel =
|
||||
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
||||
new CollectionListModel<MPUser<?>>( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
||||
|
||||
protected FilesPanel() {
|
||||
setOpaque( false );
|
||||
|
@ -34,6 +34,7 @@ import javax.annotation.Nullable;
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import javax.swing.event.DocumentListener;
|
||||
import javax.swing.text.PlainDocument;
|
||||
|
||||
|
||||
/**
|
||||
@ -470,8 +471,10 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
"Show site settings." );
|
||||
private final JButton questionsButton = Components.button( Res.icons().question(), event -> showSiteQuestions(),
|
||||
"Show site recovery questions." );
|
||||
private final JButton editButton = Components.button( Res.icons().edit(), event -> showEditSite(),
|
||||
private final JButton editButton = Components.button( Res.icons().edit(), event -> showSiteValues(),
|
||||
"Set/save personal password/login." );
|
||||
private final JButton keyButton = Components.button( Res.icons().key(), event -> showSiteKeys(),
|
||||
"Cryptographic site keys." );
|
||||
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteSite(),
|
||||
"Delete the site from the user." );
|
||||
|
||||
@ -502,9 +505,12 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
siteToolbar.add( settingsButton );
|
||||
siteToolbar.add( questionsButton );
|
||||
siteToolbar.add( editButton );
|
||||
siteToolbar.add( keyButton );
|
||||
siteToolbar.add( deleteButton );
|
||||
settingsButton.setEnabled( false );
|
||||
questionsButton.setEnabled( false );
|
||||
editButton.setEnabled( false );
|
||||
keyButton.setEnabled( false );
|
||||
deleteButton.setEnabled( false );
|
||||
|
||||
answerLabel = Components.label( "Answer:" );
|
||||
@ -665,7 +671,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
} );
|
||||
}
|
||||
|
||||
public void showEditSite() {
|
||||
public void showSiteValues() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
if (site == null)
|
||||
return;
|
||||
@ -710,6 +716,76 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
}
|
||||
}
|
||||
|
||||
public void showSiteKeys() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
if (site == null)
|
||||
return;
|
||||
|
||||
JTextArea resultField = Components.textArea();
|
||||
resultField.setEnabled( false );
|
||||
|
||||
CollectionListModel<MPKeyPurpose> purposeModel = new CollectionListModel<>( MPKeyPurpose.values() );
|
||||
DocumentModel contextModel = new DocumentModel( new PlainDocument() );
|
||||
UnsignedIntegerModel counterModel = new UnsignedIntegerModel( UnsignedInteger.ONE );
|
||||
CollectionListModel<MPResultType> typeModel = new CollectionListModel<>( MPResultType.values() );
|
||||
DocumentModel stateModel = new DocumentModel( new PlainDocument() );
|
||||
|
||||
Runnable trigger = () -> Res.job( () -> {
|
||||
try {
|
||||
MPKeyPurpose purpose = purposeModel.getSelectedItem();
|
||||
MPResultType type = typeModel.getSelectedItem();
|
||||
|
||||
String result = ((purpose == null) || (type == null))? null:
|
||||
site.getResult( purpose, contextModel.getText(), counterModel.getNumber(), type, stateModel.getText() );
|
||||
|
||||
Res.ui( () -> resultField.setText( result ) );
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||
logger.err( e, "While computing site edit results." );
|
||||
}
|
||||
} );
|
||||
|
||||
purposeModel.selection( MPKeyPurpose.Authentication, p -> trigger.run() );
|
||||
contextModel.selection( c -> trigger.run() );
|
||||
counterModel.selection( c -> trigger.run() );
|
||||
typeModel.selection( MPResultType.DeriveKey, t -> {
|
||||
switch (t) {
|
||||
case DeriveKey:
|
||||
stateModel.setText( Integer.toString( site.getAlgorithm().mpw_keySize_min() ) );
|
||||
break;
|
||||
|
||||
default:
|
||||
stateModel.setText( null );
|
||||
}
|
||||
|
||||
trigger.run();
|
||||
} );
|
||||
stateModel.selection( c -> trigger.run() );
|
||||
|
||||
if (JOptionPane.OK_OPTION == Components.showDialog( this, site.getSiteName(), new JOptionPane( Components.panel(
|
||||
BoxLayout.PAGE_AXIS,
|
||||
Components.heading( "Key Calculator" ),
|
||||
Components.label( "Purpose:" ),
|
||||
Components.comboBox( purposeModel, MPKeyPurpose::getShortName ),
|
||||
Components.strut(),
|
||||
Components.label( "Context:" ),
|
||||
Components.textField( contextModel.getDocument() ),
|
||||
Components.label( "Counter:" ),
|
||||
Components.spinner( counterModel ),
|
||||
Components.label( "Type:" ),
|
||||
Components.comboBox( typeModel, this::getTypeDescription ),
|
||||
Components.label( "State:" ),
|
||||
Components.scrollPane( Components.textField( stateModel.getDocument() ) ),
|
||||
Components.strut(),
|
||||
resultField ) ) {
|
||||
{
|
||||
setOptions( new Object[]{ "Copy", "Cancel" } );
|
||||
setInitialValue( getOptions()[0] );
|
||||
}
|
||||
} ))
|
||||
copyResult( resultField.getText() );
|
||||
}
|
||||
|
||||
public void deleteSite() {
|
||||
MPSite<?> site = sitesModel.getSelectedItem();
|
||||
if (site == null)
|
||||
@ -790,6 +866,8 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
|
||||
passwordField.setText( (result != null)? result: " " );
|
||||
settingsButton.setEnabled( result != null );
|
||||
questionsButton.setEnabled( result != null );
|
||||
editButton.setEnabled( result != null );
|
||||
keyButton.setEnabled( result != null );
|
||||
deleteButton.setEnabled( result != null );
|
||||
} ) ) );
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Loading…
Reference in New Issue
Block a user