Java improvements.
UI threading improvements. Save user/site changes to file. Ordering of user / site fixes. Add questions to JSON output. Bring JSON output format in line with C.
This commit is contained in:
parent
529f1feace
commit
954c4f8d63
@ -45,8 +45,6 @@ public class MPAlgorithmV0 extends MPAlgorithm {
|
|||||||
Native.load( MPAlgorithmV0.class, "mpw" );
|
Native.load( MPAlgorithmV0.class, "mpw" );
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Version version = MPAlgorithm.Version.V0;
|
|
||||||
|
|
||||||
protected final Logger logger = Logger.get( getClass() );
|
protected final Logger logger = Logger.get( getClass() );
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -22,7 +22,6 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
|||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.io.ByteSource;
|
import com.google.common.io.ByteSource;
|
||||||
import com.google.common.io.CharSource;
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||||
import com.lyndir.masterpassword.gui.view.PasswordFrame;
|
import com.lyndir.masterpassword.gui.view.PasswordFrame;
|
||||||
@ -51,6 +50,9 @@ public class GUI implements UnlockFrame.SignInCallback {
|
|||||||
private PasswordFrame<?, ?> passwordFrame;
|
private PasswordFrame<?, ?> passwordFrame;
|
||||||
|
|
||||||
public static void main(final String... args) {
|
public static void main(final String... args) {
|
||||||
|
Thread.setDefaultUncaughtExceptionHandler(
|
||||||
|
(t, e) -> logger.err( e, "Uncaught: %s", e.getLocalizedMessage() ) );
|
||||||
|
|
||||||
if (Config.get().checkForUpdates())
|
if (Config.get().checkForUpdates())
|
||||||
checkUpdate();
|
checkUpdate();
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ package com.lyndir.masterpassword.gui;
|
|||||||
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.Throwables;
|
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.io.Resources;
|
||||||
import com.google.common.util.concurrent.JdkFutureAdapters;
|
import com.google.common.util.concurrent.JdkFutureAdapters;
|
||||||
@ -50,17 +49,19 @@ import org.jetbrains.annotations.NonNls;
|
|||||||
@SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection" })
|
@SuppressWarnings({ "HardcodedFileSeparator", "MethodReturnAlwaysConstant", "SpellCheckingInspection" })
|
||||||
public abstract class Res {
|
public abstract class Res {
|
||||||
|
|
||||||
private static final int AVATAR_COUNT = 19;
|
private static final int AVATAR_COUNT = 19;
|
||||||
private static final Map<Window, ScheduledExecutorService> executorByWindow = new WeakHashMap<>();
|
private static final Map<Window, ScheduledExecutorService> jobExecutorByWindow = new WeakHashMap<>();
|
||||||
private static final Logger logger = Logger.get( Res.class );
|
private static final Executor immediateUiExecutor = new SwingExecutorService( true );
|
||||||
private static final Colors colors = new Colors();
|
private static final Executor laterUiExecutor = new SwingExecutorService( false );
|
||||||
|
private static final Logger logger = Logger.get( Res.class );
|
||||||
|
private static final Colors colors = new Colors();
|
||||||
|
|
||||||
public static Future<?> execute(final Window host, final Runnable job) {
|
public static Future<?> job(final Window host, final Runnable job) {
|
||||||
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
|
return job( host, job, 0, TimeUnit.MILLISECONDS );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Future<?> schedule(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) {
|
public static Future<?> job(final Window host, final Runnable job, final long delay, final TimeUnit timeUnit) {
|
||||||
return getExecutor( host ).schedule( () -> {
|
return jobExecutor( host ).schedule( () -> {
|
||||||
try {
|
try {
|
||||||
job.run();
|
job.run();
|
||||||
}
|
}
|
||||||
@ -70,33 +71,29 @@ public abstract class Res {
|
|||||||
}, delay, timeUnit );
|
}, delay, timeUnit );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <V> ListenableFuture<V> execute(final Window host, final Callable<V> job) {
|
public static <V> ListenableFuture<V> job(final Window host, final Callable<V> job) {
|
||||||
return schedule( host, job, 0, TimeUnit.MILLISECONDS );
|
return job( host, job, 0, TimeUnit.MILLISECONDS );
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <V> ListenableFuture<V> schedule(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) {
|
public static <V> ListenableFuture<V> job(final Window host, final Callable<V> job, final long delay, final TimeUnit timeUnit) {
|
||||||
ScheduledExecutorService executor = getExecutor( host );
|
ScheduledExecutorService executor = jobExecutor( host );
|
||||||
return JdkFutureAdapters.listenInPoolThread( executor.schedule( () -> {
|
return JdkFutureAdapters.listenInPoolThread( executor.schedule( job::call, delay, timeUnit ), executor );
|
||||||
try {
|
|
||||||
return job.call();
|
|
||||||
}
|
|
||||||
catch (final Throwable t) {
|
|
||||||
logger.err( t, "Unexpected: %s", t.getLocalizedMessage() );
|
|
||||||
throw Throwables.propagate( t );
|
|
||||||
}
|
|
||||||
}, delay, timeUnit ), executor );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ScheduledExecutorService getExecutor(final Window host) {
|
public static Executor uiExecutor(final boolean immediate) {
|
||||||
ScheduledExecutorService executor = executorByWindow.get( host );
|
return immediate? immediateUiExecutor: laterUiExecutor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ScheduledExecutorService jobExecutor(final Window host) {
|
||||||
|
ScheduledExecutorService executor = jobExecutorByWindow.get( host );
|
||||||
|
|
||||||
if (executor == null) {
|
if (executor == null) {
|
||||||
executorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
|
jobExecutorByWindow.put( host, executor = Executors.newSingleThreadScheduledExecutor() );
|
||||||
|
|
||||||
host.addWindowListener( new WindowAdapter() {
|
host.addWindowListener( new WindowAdapter() {
|
||||||
@Override
|
@Override
|
||||||
public void windowClosed(final WindowEvent e) {
|
public void windowClosed(final WindowEvent e) {
|
||||||
ExecutorService executor = executorByWindow.remove( host );
|
ExecutorService executor = jobExecutorByWindow.remove( host );
|
||||||
if (executor != null)
|
if (executor != null)
|
||||||
executor.shutdownNow();
|
executor.shutdownNow();
|
||||||
}
|
}
|
||||||
@ -204,7 +201,7 @@ public abstract class Res {
|
|||||||
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
|
font = Font.createFont( Font.TRUETYPE_FONT, Resources.getResource( fontResourceName ).openStream() ) ) );
|
||||||
}
|
}
|
||||||
catch (final FontFormatException | IOException e) {
|
catch (final FontFormatException | IOException e) {
|
||||||
throw Throwables.propagate( e );
|
throw logger.bug( e );
|
||||||
}
|
}
|
||||||
|
|
||||||
return font;
|
return font;
|
||||||
|
@ -0,0 +1,91 @@
|
|||||||
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.collect.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import javax.swing.*;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-08
|
||||||
|
*/
|
||||||
|
public class SwingExecutorService extends AbstractExecutorService {
|
||||||
|
|
||||||
|
private final List<Runnable> pendingCommands = Lists.newLinkedList();
|
||||||
|
private final BlockingQueue<Boolean> terminated = Queues.newLinkedBlockingDeque( 1 );
|
||||||
|
private final boolean immediate;
|
||||||
|
private boolean shutdown;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param immediate Allow immediate execution of the job in {@link #execute(Runnable)} if already on the right thread.
|
||||||
|
* If {@code false}, jobs are always posted for later execution on the event thread.
|
||||||
|
*/
|
||||||
|
public SwingExecutorService(final boolean immediate) {
|
||||||
|
this.immediate = immediate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
shutdown = true;
|
||||||
|
|
||||||
|
synchronized (pendingCommands) {
|
||||||
|
if (pendingCommands.isEmpty())
|
||||||
|
terminated.offer( true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
synchronized (pendingCommands) {
|
||||||
|
return ImmutableList.copyOf( pendingCommands );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
return ifNotNullElse( terminated.peek(), false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(final long timeout, @NotNull final TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
|
return ifNotNullElse( terminated.poll( timeout, unit ), false );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(@NotNull final Runnable command) {
|
||||||
|
if (shutdown)
|
||||||
|
throw new RejectedExecutionException( "Executor is shut down." );
|
||||||
|
|
||||||
|
synchronized (pendingCommands) {
|
||||||
|
pendingCommands.add( command );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (immediate && SwingUtilities.isEventDispatchThread())
|
||||||
|
run( command );
|
||||||
|
else
|
||||||
|
SwingUtilities.invokeLater( () -> run( command ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void run(final Runnable command) {
|
||||||
|
command.run();
|
||||||
|
|
||||||
|
synchronized (pendingCommands) {
|
||||||
|
pendingCommands.remove( command );
|
||||||
|
|
||||||
|
if (shutdown && pendingCommands.isEmpty())
|
||||||
|
terminated.offer( true );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ import com.google.common.primitives.UnsignedInteger;
|
|||||||
import com.lyndir.masterpassword.MPAlgorithm;
|
import com.lyndir.masterpassword.MPAlgorithm;
|
||||||
import com.lyndir.masterpassword.MPResultType;
|
import com.lyndir.masterpassword.MPResultType;
|
||||||
import com.lyndir.masterpassword.model.impl.MPBasicSite;
|
import com.lyndir.masterpassword.model.impl.MPBasicSite;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
@ -44,6 +45,7 @@ public class MPIncognitoSite extends MPBasicSite<MPIncognitoQuestion> {
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPIncognitoUser getUser() {
|
public MPIncognitoUser getUser() {
|
||||||
return user;
|
return user;
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.util;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-08
|
||||||
|
*/
|
||||||
|
public abstract class FailableCallback<T> implements FutureCallback<T> {
|
||||||
|
|
||||||
|
private final Logger logger;
|
||||||
|
|
||||||
|
protected FailableCallback(final Logger logger) {
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
logger.err( t, "Future failed." );
|
||||||
|
onSuccess( null );
|
||||||
|
}
|
||||||
|
}
|
@ -23,11 +23,12 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
|||||||
|
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.gui.Res;
|
import com.lyndir.masterpassword.gui.Res;
|
||||||
import com.lyndir.masterpassword.gui.util.Components;
|
import com.lyndir.masterpassword.gui.util.*;
|
||||||
import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel;
|
|
||||||
import com.lyndir.masterpassword.model.MPSite;
|
import com.lyndir.masterpassword.model.MPSite;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import com.lyndir.masterpassword.model.impl.MPFileSite;
|
import com.lyndir.masterpassword.model.impl.MPFileSite;
|
||||||
@ -35,7 +36,7 @@ import com.lyndir.masterpassword.model.impl.MPFileUser;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.datatransfer.StringSelection;
|
import java.awt.datatransfer.StringSelection;
|
||||||
import java.awt.datatransfer.Transferable;
|
import java.awt.datatransfer.Transferable;
|
||||||
import java.awt.event.*;
|
import java.awt.event.WindowEvent;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
@ -50,6 +51,8 @@ import javax.swing.event.DocumentListener;
|
|||||||
*/
|
*/
|
||||||
public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> extends JFrame implements DocumentListener {
|
public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> extends JFrame implements DocumentListener {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.get( PasswordFrame.class );
|
||||||
|
|
||||||
@SuppressWarnings("FieldCanBeLocal")
|
@SuppressWarnings("FieldCanBeLocal")
|
||||||
private final Components.GradientPanel root;
|
private final Components.GradientPanel root;
|
||||||
private final JTextField siteNameField;
|
private final JTextField siteNameField;
|
||||||
@ -96,40 +99,33 @@ public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> ex
|
|||||||
siteNameField = Components.textField(), Components.stud(),
|
siteNameField = Components.textField(), Components.stud(),
|
||||||
siteActionButton = Components.button( "Add Site" ) );
|
siteActionButton = Components.button( "Add Site" ) );
|
||||||
siteNameField.getDocument().addDocumentListener( this );
|
siteNameField.getDocument().addDocumentListener( this );
|
||||||
siteNameField.addActionListener( new ActionListener() {
|
siteNameField.addActionListener(
|
||||||
@Override
|
e -> Futures.addCallback( updatePassword( true ), new FailableCallback<String>( logger ) {
|
||||||
public void actionPerformed(final ActionEvent e) {
|
|
||||||
Futures.addCallback( updatePassword( true ), new FutureCallback<String>() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@Nullable final String sitePassword) {
|
public void onSuccess(@Nullable final String sitePassword) {
|
||||||
|
if (sitePassword == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (currentSite instanceof MPFileSite)
|
||||||
|
((MPFileSite) currentSite).use();
|
||||||
|
|
||||||
Transferable clipboardContents = new StringSelection( sitePassword );
|
Transferable clipboardContents = new StringSelection( sitePassword );
|
||||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||||
|
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||||
SwingUtilities.invokeLater( () -> {
|
|
||||||
passwordField.setText( null );
|
|
||||||
siteNameField.setText( null );
|
|
||||||
|
|
||||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
|
||||||
} );
|
|
||||||
}
|
}
|
||||||
|
}, Res.uiExecutor( false ) ) );
|
||||||
|
siteActionButton.addActionListener(
|
||||||
|
e -> {
|
||||||
|
if (currentSite == null)
|
||||||
|
return;
|
||||||
|
if (currentSite instanceof MPFileSite)
|
||||||
|
this.user.deleteSite( currentSite );
|
||||||
|
else
|
||||||
|
this.user.addSite( currentSite );
|
||||||
|
siteNameField.requestFocus();
|
||||||
|
|
||||||
@Override
|
updatePassword( true );
|
||||||
public void onFailure(@Nonnull final Throwable t) {
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
}
|
|
||||||
} );
|
|
||||||
siteActionButton.addActionListener( e -> {
|
|
||||||
if (currentSite == null)
|
|
||||||
return;
|
|
||||||
if (currentSite instanceof MPFileSite)
|
|
||||||
this.user.deleteSite( currentSite );
|
|
||||||
else
|
|
||||||
this.user.addSite( currentSite );
|
|
||||||
siteNameField.requestFocus();
|
|
||||||
|
|
||||||
updatePassword( true );
|
|
||||||
} );
|
|
||||||
sitePanel.add( siteControls );
|
sitePanel.add( siteControls );
|
||||||
sitePanel.add( Components.stud() );
|
sitePanel.add( Components.stud() );
|
||||||
|
|
||||||
@ -229,34 +225,36 @@ public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite<?>> ex
|
|||||||
site.setCounter( siteCounter );
|
site.setCounter( siteCounter );
|
||||||
}
|
}
|
||||||
|
|
||||||
ListenableFuture<String> passwordFuture = Res.execute( this, () -> site.getResult( MPKeyPurpose.Authentication, null, null ) );
|
ListenableFuture<String> passwordFuture = Res.job( this, () ->
|
||||||
Futures.addCallback( passwordFuture, new FutureCallback<String>() {
|
site.getResult( MPKeyPurpose.Authentication, null, null ) );
|
||||||
@Override
|
|
||||||
public void onSuccess(@Nullable final String sitePassword) {
|
SwingUtilities.invokeLater( () -> {
|
||||||
SwingUtilities.invokeLater( () -> {
|
updatingUI = true;
|
||||||
updatingUI = true;
|
currentSite = site;
|
||||||
currentSite = site;
|
siteActionButton.setVisible( user instanceof MPFileUser );
|
||||||
siteActionButton.setVisible( user instanceof MPFileUser );
|
if (currentSite instanceof MPFileSite)
|
||||||
if (currentSite instanceof MPFileSite)
|
siteActionButton.setText( "Delete Site" );
|
||||||
siteActionButton.setText( "Delete Site" );
|
else
|
||||||
else
|
siteActionButton.setText( "Add Site" );
|
||||||
siteActionButton.setText( "Add Site" );
|
resultTypeField.setSelectedItem( currentSite.getResultType() );
|
||||||
resultTypeField.setSelectedItem( currentSite.getResultType() );
|
siteVersionField.setSelectedItem( currentSite.getAlgorithm().version() );
|
||||||
siteVersionField.setSelectedItem( currentSite.getAlgorithm() );
|
siteCounterField.setValue( currentSite.getCounter() );
|
||||||
siteCounterField.setValue( currentSite.getCounter() );
|
siteNameField.setText( currentSite.getName() );
|
||||||
siteNameField.setText( currentSite.getName() );
|
if (siteNameField.getText().startsWith( siteNameQuery ))
|
||||||
if (siteNameField.getText().startsWith( siteNameQuery ))
|
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
|
||||||
siteNameField.select( siteNameQuery.length(), siteNameField.getText().length() );
|
passwordField.setText( null );
|
||||||
|
tipLabel.setText( "Getting password..." );
|
||||||
|
|
||||||
|
Futures.addCallback( passwordFuture, new FailableCallback<String>( logger ) {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(@Nullable final String sitePassword) {
|
||||||
|
if (sitePassword != null)
|
||||||
|
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
|
||||||
|
|
||||||
passwordField.setText( sitePassword );
|
passwordField.setText( sitePassword );
|
||||||
tipLabel.setText( "Press [Enter] to copy the password. Then paste it into the password field." );
|
|
||||||
updatingUI = false;
|
updatingUI = false;
|
||||||
} );
|
}
|
||||||
}
|
}, Res.uiExecutor( true ) );
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(@Nonnull final Throwable t) {
|
|
||||||
}
|
|
||||||
} );
|
} );
|
||||||
|
|
||||||
return passwordFuture;
|
return passwordFuture;
|
||||||
|
@ -153,7 +153,7 @@ public class UnlockFrame extends JFrame {
|
|||||||
boolean checkSignIn() {
|
boolean checkSignIn() {
|
||||||
if (identiconFuture != null)
|
if (identiconFuture != null)
|
||||||
identiconFuture.cancel( false );
|
identiconFuture.cancel( false );
|
||||||
identiconFuture = Res.schedule( this, () -> SwingUtilities.invokeLater( () -> {
|
identiconFuture = Res.job( this, () -> SwingUtilities.invokeLater( () -> {
|
||||||
String fullName = (user == null)? "": user.getFullName();
|
String fullName = (user == null)? "": user.getFullName();
|
||||||
char[] masterPassword = authenticationPanel.getMasterPassword();
|
char[] masterPassword = authenticationPanel.getMasterPassword();
|
||||||
|
|
||||||
@ -186,7 +186,7 @@ public class UnlockFrame extends JFrame {
|
|||||||
signInButton.setEnabled( false );
|
signInButton.setEnabled( false );
|
||||||
signInButton.setText( "Signing In..." );
|
signInButton.setText( "Signing In..." );
|
||||||
|
|
||||||
Res.execute( this, () -> {
|
Res.job( this, () -> {
|
||||||
try {
|
try {
|
||||||
user.authenticate( authenticationPanel.getMasterPassword() );
|
user.authenticate( authenticationPanel.getMasterPassword() );
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ package com.lyndir.masterpassword.model;
|
|||||||
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;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
@ -31,41 +32,50 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
|||||||
|
|
||||||
// - Meta
|
// - Meta
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
void setName(String name);
|
void setName(String name);
|
||||||
|
|
||||||
// - Algorithm
|
// - Algorithm
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
MPAlgorithm getAlgorithm();
|
MPAlgorithm getAlgorithm();
|
||||||
|
|
||||||
void setAlgorithm(MPAlgorithm algorithm);
|
void setAlgorithm(MPAlgorithm algorithm);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
UnsignedInteger getCounter();
|
UnsignedInteger getCounter();
|
||||||
|
|
||||||
void setCounter(UnsignedInteger counter);
|
void setCounter(UnsignedInteger counter);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
MPResultType getResultType();
|
MPResultType getResultType();
|
||||||
|
|
||||||
void setResultType(MPResultType resultType);
|
void setResultType(MPResultType resultType);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
MPResultType getLoginType();
|
MPResultType getLoginType();
|
||||||
|
|
||||||
void setLoginType(@Nullable MPResultType loginType);
|
void setLoginType(@Nullable MPResultType loginType);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
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 getLogin(@Nullable String state)
|
String getLogin(@Nullable String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException;
|
throws MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
|
||||||
// - Relations
|
// - Relations
|
||||||
|
|
||||||
MPUser<? extends MPSite<?>> getUser();
|
@Nonnull
|
||||||
|
MPUser<?> getUser();
|
||||||
|
|
||||||
void addQuestion(Q question);
|
void addQuestion(Q question);
|
||||||
|
|
||||||
void deleteQuestion(Q question);
|
void deleteQuestion(Q question);
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
Collection<Q> getQuestions();
|
Collection<Q> getQuestions();
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,7 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.*;
|
||||||
import com.google.common.collect.Maps;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ public abstract class MPUserManager<U extends MPUser<?>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Collection<U> getUsers() {
|
public Collection<U> getUsers() {
|
||||||
return ImmutableList.copyOf( usersByName.values() );
|
return ImmutableSortedSet.copyOf( usersByName.values() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public U getUserNamed(final String fullName) {
|
public U getUserNamed(final String fullName) {
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.lyndir.masterpassword.model.impl;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-08
|
||||||
|
*/
|
||||||
|
public class Changeable {
|
||||||
|
|
||||||
|
private static final ExecutorService changeExecutor = Executors.newSingleThreadExecutor();
|
||||||
|
|
||||||
|
private boolean changed;
|
||||||
|
private boolean batchingChanges;
|
||||||
|
|
||||||
|
void setChanged() {
|
||||||
|
synchronized (changeExecutor) {
|
||||||
|
if (changed)
|
||||||
|
return;
|
||||||
|
changed = true;
|
||||||
|
|
||||||
|
if (batchingChanges)
|
||||||
|
return;
|
||||||
|
|
||||||
|
changeExecutor.submit( () -> {
|
||||||
|
synchronized (changeExecutor) {
|
||||||
|
if (batchingChanges)
|
||||||
|
return;
|
||||||
|
changed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChanged();
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void onChanged() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void beginChanges() {
|
||||||
|
synchronized (changeExecutor) {
|
||||||
|
batchingChanges = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean endChanges() {
|
||||||
|
synchronized (changeExecutor) {
|
||||||
|
batchingChanges = false;
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
this.changed = false;
|
||||||
|
setChanged();
|
||||||
|
return true;
|
||||||
|
} else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 2018-05-14
|
* @author lhunath, 2018-05-14
|
||||||
*/
|
*/
|
||||||
public abstract class MPBasicQuestion implements MPQuestion {
|
public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
|
||||||
|
|
||||||
private final String keyword;
|
private final String keyword;
|
||||||
private MPResultType type;
|
private MPResultType type;
|
||||||
@ -56,6 +56,8 @@ public abstract class MPBasicQuestion implements MPQuestion {
|
|||||||
@Override
|
@Override
|
||||||
public void setType(final MPResultType type) {
|
public void setType(final MPResultType type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -70,6 +72,13 @@ public abstract class MPBasicQuestion implements MPQuestion {
|
|||||||
@Override
|
@Override
|
||||||
public abstract MPBasicSite<?> getSite();
|
public abstract MPBasicSite<?> getSite();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
|
||||||
|
getSite().setChanged();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode( getKeyword() );
|
return Objects.hashCode( getKeyword() );
|
||||||
|
@ -26,6 +26,7 @@ import com.lyndir.masterpassword.*;
|
|||||||
import com.lyndir.masterpassword.model.MPQuestion;
|
import com.lyndir.masterpassword.model.MPQuestion;
|
||||||
import com.lyndir.masterpassword.model.MPSite;
|
import com.lyndir.masterpassword.model.MPSite;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 14-12-16
|
* @author lhunath, 14-12-16
|
||||||
*/
|
*/
|
||||||
public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable implements MPSite<Q> {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private MPAlgorithm algorithm;
|
private MPAlgorithm algorithm;
|
||||||
@ -56,6 +57,7 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
this.loginType = (loginType == null)? algorithm.mpw_default_login_type(): loginType;
|
this.loginType = (loginType == null)? algorithm.mpw_default_login_type(): loginType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
@ -64,8 +66,11 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
@Override
|
@Override
|
||||||
public void setName(final String name) {
|
public void setName(final String name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPAlgorithm getAlgorithm() {
|
public MPAlgorithm getAlgorithm() {
|
||||||
return algorithm;
|
return algorithm;
|
||||||
@ -74,8 +79,11 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
@Override
|
@Override
|
||||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||||
this.algorithm = algorithm;
|
this.algorithm = algorithm;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public UnsignedInteger getCounter() {
|
public UnsignedInteger getCounter() {
|
||||||
return counter;
|
return counter;
|
||||||
@ -84,8 +92,11 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
@Override
|
@Override
|
||||||
public void setCounter(final UnsignedInteger counter) {
|
public void setCounter(final UnsignedInteger counter) {
|
||||||
this.counter = counter;
|
this.counter = counter;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPResultType getResultType() {
|
public MPResultType getResultType() {
|
||||||
return resultType;
|
return resultType;
|
||||||
@ -94,8 +105,11 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
@Override
|
@Override
|
||||||
public void setResultType(final MPResultType resultType) {
|
public void setResultType(final MPResultType resultType) {
|
||||||
this.resultType = resultType;
|
this.resultType = resultType;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPResultType getLoginType() {
|
public MPResultType getLoginType() {
|
||||||
return loginType;
|
return loginType;
|
||||||
@ -104,8 +118,11 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
@Override
|
@Override
|
||||||
public void setLoginType(@Nullable final MPResultType loginType) {
|
public void setLoginType(@Nullable final MPResultType loginType) {
|
||||||
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
this.loginType = ifNotNullElse( loginType, getAlgorithm().mpw_default_login_type() );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable final String state)
|
public String getResult(final MPKeyPurpose keyPurpose, @Nullable final String keyContext, @Nullable final String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
@ -131,6 +148,7 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
keyPurpose, keyContext, type, state );
|
keyPurpose, keyContext, type, state );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String getLogin(@Nullable final String state)
|
public String getLogin(@Nullable final String state)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
@ -141,18 +159,34 @@ public abstract class MPBasicSite<Q extends MPQuestion> implements MPSite<Q> {
|
|||||||
@Override
|
@Override
|
||||||
public void addQuestion(final Q question) {
|
public void addQuestion(final Q question) {
|
||||||
questions.add( question );
|
questions.add( question );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteQuestion(final Q question) {
|
public void deleteQuestion(final Q question) {
|
||||||
questions.remove( question );
|
questions.remove( question );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public Collection<Q> getQuestions() {
|
public Collection<Q> getQuestions() {
|
||||||
return Collections.unmodifiableCollection( questions );
|
return Collections.unmodifiableCollection( questions );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
@Override
|
||||||
|
public abstract MPBasicUser<?> getUser();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
|
||||||
|
getUser().setChanged();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode( getName() );
|
return Objects.hashCode( getName() );
|
||||||
|
@ -20,7 +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.collect.ImmutableList;
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
@ -34,7 +34,7 @@ import javax.annotation.Nullable;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 2014-06-08
|
* @author lhunath, 2014-06-08
|
||||||
*/
|
*/
|
||||||
public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S> {
|
public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable implements MPUser<S> {
|
||||||
|
|
||||||
protected final Logger logger = Logger.get( getClass() );
|
protected final Logger logger = Logger.get( getClass() );
|
||||||
|
|
||||||
@ -64,6 +64,8 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S>
|
|||||||
@Override
|
@Override
|
||||||
public void setAvatar(final int avatar) {
|
public void setAvatar(final int avatar) {
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -81,6 +83,8 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S>
|
|||||||
@Override
|
@Override
|
||||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||||
this.algorithm = algorithm;
|
this.algorithm = algorithm;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -136,7 +140,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S>
|
|||||||
public MPMasterKey getMasterKey()
|
public MPMasterKey getMasterKey()
|
||||||
throws MPKeyUnavailableException {
|
throws MPKeyUnavailableException {
|
||||||
if (masterKey == null)
|
if (masterKey == null)
|
||||||
throw new MPKeyUnavailableException( "Master key was not yet set." );
|
throw new MPKeyUnavailableException( "Master key was not yet set for: " + this );
|
||||||
|
|
||||||
return masterKey;
|
return masterKey;
|
||||||
}
|
}
|
||||||
@ -144,11 +148,15 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S>
|
|||||||
@Override
|
@Override
|
||||||
public void addSite(final S site) {
|
public void addSite(final S site) {
|
||||||
sites.add( site );
|
sites.add( site );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteSite(final S site) {
|
public void deleteSite(final S site) {
|
||||||
sites.remove( site );
|
sites.remove( site );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -160,7 +168,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> implements MPUser<S>
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public Collection<S> findSites(final String query) {
|
public Collection<S> findSites(final String query) {
|
||||||
ImmutableList.Builder<S> results = ImmutableList.builder();
|
ImmutableSortedSet.Builder<S> results = ImmutableSortedSet.naturalOrder();
|
||||||
for (final S site : getSites())
|
for (final S site : getSites())
|
||||||
if (site.getName().startsWith( query ))
|
if (site.getName().startsWith( query ))
|
||||||
results.add( site );
|
results.add( site );
|
||||||
|
@ -33,19 +33,24 @@ public class MPFileQuestion extends MPBasicQuestion {
|
|||||||
private final MPFileSite site;
|
private final MPFileSite site;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private String state;
|
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 state) {
|
@Nullable final MPResultType type, @Nullable final String answerState) {
|
||||||
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
super( keyword, ifNotNullElse( type, site.getAlgorithm().mpw_default_answer_type() ) );
|
||||||
|
|
||||||
this.site = site;
|
this.site = site;
|
||||||
this.state = state;
|
this.answerState = answerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public String getAnswerState() {
|
||||||
|
return answerState;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAnswer()
|
public String getAnswer()
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
return getAnswer( state );
|
return getAnswer( answerState );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAnswer(final MPResultType type, @Nullable final String answer)
|
public void setAnswer(final MPResultType type, @Nullable final String answer)
|
||||||
@ -53,10 +58,12 @@ public class MPFileQuestion extends MPBasicQuestion {
|
|||||||
setType( type );
|
setType( type );
|
||||||
|
|
||||||
if (answer == null)
|
if (answer == null)
|
||||||
this.state = null;
|
this.answerState = null;
|
||||||
else
|
else
|
||||||
this.state = getSite().getState(
|
this.answerState = getSite().getState(
|
||||||
MPKeyPurpose.Recovery, getKeyword(), null, getType(), answer );
|
MPKeyPurpose.Recovery, getKeyword(), null, getType(), answer );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -20,6 +20,7 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
|
|
||||||
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.MPSite;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.joda.time.Instant;
|
import org.joda.time.Instant;
|
||||||
@ -29,6 +30,7 @@ import org.joda.time.ReadableInstant;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 14-12-05
|
* @author lhunath, 14-12-05
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
||||||
public class MPFileSite extends MPBasicSite<MPFileQuestion> {
|
public class MPFileSite extends MPBasicSite<MPFileQuestion> {
|
||||||
|
|
||||||
private final MPFileUser user;
|
private final MPFileUser user;
|
||||||
@ -77,6 +79,8 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
|
|||||||
|
|
||||||
public void setUrl(@Nullable final String url) {
|
public void setUrl(@Nullable final String url) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getUses() {
|
public int getUses() {
|
||||||
@ -125,6 +129,8 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
|
|||||||
else
|
else
|
||||||
this.resultState = getState(
|
this.resultState = getState(
|
||||||
MPKeyPurpose.Authentication, null, getCounter(), getResultType(), password );
|
MPKeyPurpose.Authentication, null, getCounter(), getResultType(), password );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -141,10 +147,22 @@ public class MPFileSite extends MPBasicSite<MPFileQuestion> {
|
|||||||
else
|
else
|
||||||
this.loginState = getState(
|
this.loginState = getState(
|
||||||
MPKeyPurpose.Identification, null, null, getLoginType(), loginName );
|
MPKeyPurpose.Identification, null, null, getLoginType(), loginName );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPFileUser getUser() {
|
public MPFileUser getUser() {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(final MPSite<?> o) {
|
||||||
|
int comparison = (o instanceof MPFileSite)? -getLastUsed().compareTo( ((MPFileSite) o).getLastUsed() ): 0;
|
||||||
|
if (comparison != 0)
|
||||||
|
return comparison;
|
||||||
|
|
||||||
|
return super.compareTo( o );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model.impl;
|
package com.lyndir.masterpassword.model.impl;
|
||||||
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
@ -34,9 +33,6 @@ import org.joda.time.ReadableInstant;
|
|||||||
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
||||||
public class MPFileUser extends MPBasicUser<MPFileSite> {
|
public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
|
||||||
private static final Logger logger = Logger.get( MPFileUser.class );
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private byte[] keyID;
|
private byte[] keyID;
|
||||||
private MPMarshalFormat format;
|
private MPMarshalFormat format;
|
||||||
@ -101,6 +97,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
public void setFormat(final MPMarshalFormat format) {
|
public void setFormat(final MPMarshalFormat format) {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPMarshaller.ContentMode getContentMode() {
|
public MPMarshaller.ContentMode getContentMode() {
|
||||||
@ -109,6 +107,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
|
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
|
||||||
this.contentMode = contentMode;
|
this.contentMode = contentMode;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPResultType getDefaultType() {
|
public MPResultType getDefaultType() {
|
||||||
@ -117,6 +117,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
public void setDefaultType(final MPResultType defaultType) {
|
public void setDefaultType(final MPResultType defaultType) {
|
||||||
this.defaultType = defaultType;
|
this.defaultType = defaultType;
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ReadableInstant getLastUsed() {
|
public ReadableInstant getLastUsed() {
|
||||||
@ -125,6 +127,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
public void use() {
|
public void use() {
|
||||||
lastUsed = new Instant();
|
lastUsed = new Instant();
|
||||||
|
|
||||||
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJSON(final MPJSONFile json) {
|
public void setJSON(final MPJSONFile json) {
|
||||||
@ -141,8 +145,23 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||||
super.authenticate( masterKey );
|
super.authenticate( masterKey );
|
||||||
|
|
||||||
if (keyID == null)
|
if (keyID == null) {
|
||||||
keyID = masterKey.getKeyID( getAlgorithm() );
|
keyID = masterKey.getKeyID( getAlgorithm() );
|
||||||
|
|
||||||
|
setChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
|
||||||
|
try {
|
||||||
|
save();
|
||||||
|
}
|
||||||
|
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||||
|
logger.wrn( e, "Couldn't save change." );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void save()
|
void save()
|
||||||
@ -152,7 +171,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int compareTo(final MPUser<?> o) {
|
public int compareTo(final MPUser<?> o) {
|
||||||
int comparison = (o instanceof MPFileUser)? getLastUsed().compareTo( ((MPFileUser) o).getLastUsed() ): 0;
|
int comparison = (o instanceof MPFileUser)? -getLastUsed().compareTo( ((MPFileUser) o).getLastUsed() ): 0;
|
||||||
if (comparison != 0)
|
if (comparison != 0)
|
||||||
return comparison;
|
return comparison;
|
||||||
|
|
||||||
|
@ -18,14 +18,14 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model.impl;
|
package com.lyndir.masterpassword.model.impl;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
import com.fasterxml.jackson.annotation.*;
|
||||||
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2018-05-14
|
* @author lhunath, 2018-05-14
|
||||||
*/
|
*/
|
||||||
|
@JsonInclude(value = JsonInclude.Include.CUSTOM, valueFilter = MPJSONAnyObject.MPJSONEmptyValue.class)
|
||||||
class MPJSONAnyObject {
|
class MPJSONAnyObject {
|
||||||
|
|
||||||
@JsonAnySetter
|
@JsonAnySetter
|
||||||
@ -35,4 +35,21 @@ class MPJSONAnyObject {
|
|||||||
public Map<String, Object> getAny() {
|
public Map<String, Object> getAny() {
|
||||||
return Collections.unmodifiableMap( any );
|
return Collections.unmodifiableMap( any );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EqualsAndHashcode")
|
||||||
|
public static class MPJSONEmptyValue {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@SuppressWarnings({ "ChainOfInstanceofChecks", "Contract" })
|
||||||
|
public boolean equals(final Object obj) {
|
||||||
|
if (obj instanceof Collection<?>)
|
||||||
|
return ((Collection<?>) obj).isEmpty();
|
||||||
|
if (obj instanceof Map<?, ?>)
|
||||||
|
return ((Map<?, ?>) obj).isEmpty();
|
||||||
|
if (obj instanceof MPJSONFile.Site.Ext)
|
||||||
|
return ((MPJSONAnyObject) obj).any.isEmpty();
|
||||||
|
|
||||||
|
return obj == null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,10 @@ 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 com.fasterxml.jackson.annotation.*;
|
import com.fasterxml.jackson.annotation.JsonAutoDetect;
|
||||||
|
import com.fasterxml.jackson.annotation.PropertyAccessor;
|
||||||
|
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||||
|
import com.fasterxml.jackson.core.util.Separators;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
@ -37,18 +40,28 @@ import org.joda.time.Instant;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 2018-04-27
|
* @author lhunath, 2018-04-27
|
||||||
*/
|
*/
|
||||||
@SuppressFBWarnings( "URF_UNREAD_FIELD" )
|
@SuppressFBWarnings("URF_UNREAD_FIELD")
|
||||||
public class MPJSONFile extends MPJSONAnyObject {
|
public class MPJSONFile extends MPJSONAnyObject {
|
||||||
|
|
||||||
protected static final ObjectMapper objectMapper = new ObjectMapper();
|
protected static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
objectMapper.setSerializationInclusion( JsonInclude.Include.NON_EMPTY );
|
objectMapper.setDefaultPrettyPrinter( new DefaultPrettyPrinter() {
|
||||||
|
private static final long serialVersionUID = 1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DefaultPrettyPrinter withSeparators(final Separators separators) {
|
||||||
|
super.withSeparators( separators );
|
||||||
|
_objectFieldValueSeparatorWithSpaces = separators.getObjectFieldValueSeparator() + " ";
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
} );
|
||||||
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPJSONFile write(final MPFileUser modelUser)
|
public MPJSONFile write(final MPFileUser modelUser)
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
// Section: "export"
|
// Section: "export"
|
||||||
if (export == null)
|
if (export == null)
|
||||||
export = new Export();
|
export = new Export();
|
||||||
@ -98,38 +111,27 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
site.uses = modelSite.getUses();
|
site.uses = modelSite.getUses();
|
||||||
site.last_used = MPConstants.dateTimeFormatter.print( modelSite.getLastUsed() );
|
site.last_used = MPConstants.dateTimeFormatter.print( modelSite.getLastUsed() );
|
||||||
|
|
||||||
|
if (site.questions == null)
|
||||||
|
site.questions = new LinkedHashMap<>();
|
||||||
|
for (final MPFileQuestion question : modelSite.getQuestions())
|
||||||
|
site.questions.put( question.getKeyword(), new Site.Question() {
|
||||||
|
{
|
||||||
|
type = question.getType();
|
||||||
|
|
||||||
|
if (!export.redacted) {
|
||||||
|
// Clear Text
|
||||||
|
answer = question.getAnswer();
|
||||||
|
} else {
|
||||||
|
// Redacted
|
||||||
|
if (question.getType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
||||||
|
answer = question.getAnswerState();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
if (site._ext_mpw == null)
|
if (site._ext_mpw == null)
|
||||||
site._ext_mpw = new Site.Ext();
|
site._ext_mpw = new Site.Ext();
|
||||||
site._ext_mpw.url = modelSite.getUrl();
|
site._ext_mpw.url = modelSite.getUrl();
|
||||||
|
|
||||||
if (site.questions == null)
|
|
||||||
site.questions = new LinkedHashMap<>();
|
|
||||||
// for (size_t q = 0; q < site.questions_count; ++q) {
|
|
||||||
// MPMarshalledQuestion *question = &site.questions[q];
|
|
||||||
// if (!question.keyword)
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// json_object *json_site_question = json_object_new_object();
|
|
||||||
// json_object_object_add( json_site_questions, question.keyword, json_site_question );
|
|
||||||
// json_object_object_add( json_site_question, "type = question.type;
|
|
||||||
//
|
|
||||||
// if (!user.redacted) {
|
|
||||||
// // Clear Text
|
|
||||||
// const char *answerContent = mpw_siteResult( masterKey, site.name, MPCounterValueInitial,
|
|
||||||
// MPKeyPurposeRecovery, question.keyword, question.type, question.content, site.algorithm );
|
|
||||||
// json_object_object_add( json_site_question, "answer = answerContent;
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// // Redacted
|
|
||||||
// if (site.type & MPSiteFeatureExportContent && question.content && strlen( question.content ))
|
|
||||||
// json_object_object_add( json_site_question, "answer = question.content;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// json_object *json_site_mpw = json_object_new_object();
|
|
||||||
// fileSite._ext_mpw = json_site_mpw;
|
|
||||||
// if (site.url)
|
|
||||||
// json_object_object_add( json_site_mpw, "url", site.url );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
@ -143,6 +145,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
||||||
(user.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
(user.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||||
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
||||||
|
model.beginChanges();
|
||||||
model.setJSON( this );
|
model.setJSON( this );
|
||||||
if (masterPassword != null)
|
if (masterPassword != null)
|
||||||
model.authenticate( masterPassword );
|
model.authenticate( masterPassword );
|
||||||
@ -167,6 +170,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
|
|
||||||
model.addSite( site );
|
model.addSite( site );
|
||||||
}
|
}
|
||||||
|
model.endChanges();
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
@ -193,26 +197,26 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
String full_name;
|
String full_name;
|
||||||
String last_used;
|
String last_used;
|
||||||
@Nullable
|
@Nullable
|
||||||
MPAlgorithm.Version algorithm;
|
|
||||||
@Nullable
|
|
||||||
String key_id;
|
String key_id;
|
||||||
@Nullable
|
@Nullable
|
||||||
|
MPAlgorithm.Version algorithm;
|
||||||
|
@Nullable
|
||||||
MPResultType default_type;
|
MPResultType default_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class Site extends MPJSONAnyObject {
|
public static class Site extends MPJSONAnyObject {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
MPResultType type;
|
||||||
long counter;
|
long counter;
|
||||||
MPAlgorithm.Version algorithm;
|
MPAlgorithm.Version algorithm;
|
||||||
@Nullable
|
@Nullable
|
||||||
MPResultType type;
|
|
||||||
@Nullable
|
|
||||||
String password;
|
String password;
|
||||||
@Nullable
|
@Nullable
|
||||||
MPResultType login_type;
|
|
||||||
@Nullable
|
|
||||||
String login_name;
|
String login_name;
|
||||||
|
@Nullable
|
||||||
|
MPResultType login_type;
|
||||||
|
|
||||||
int uses;
|
int uses;
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -37,7 +37,7 @@ public class MPJSONMarshaller implements MPMarshaller {
|
|||||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return objectMapper.writeValueAsString( user.getJSON().write( user ) );
|
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString( user.getJSON().write( user ) );
|
||||||
}
|
}
|
||||||
catch (final JsonProcessingException e) {
|
catch (final JsonProcessingException e) {
|
||||||
throw new MPMarshalException( "Couldn't compose JSON for: " + user, e );
|
throw new MPMarshalException( "Couldn't compose JSON for: " + user, e );
|
||||||
|
Loading…
Reference in New Issue
Block a user