Import & export users + improved user state tracking.
This commit is contained in:
parent
18ecc41b39
commit
928b617ed0
@ -44,7 +44,8 @@ public class MPMasterKey {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param masterPassword The characters of the user's master password.
|
* @param masterPassword The characters of the user's master password.
|
||||||
* <b>Note: this method destroys the contents of the array.</b>
|
*
|
||||||
|
* @apiNote This method destroys the contents of the {@code masterPassword} array.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
|
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
|
||||||
public MPMasterKey(final String fullName, final char[] masterPassword) {
|
public MPMasterKey(final String fullName, final char[] masterPassword) {
|
||||||
|
@ -5,7 +5,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
description = 'Master Password GUI'
|
description = 'Master Password GUI'
|
||||||
mainClassName = 'com.lyndir.masterpassword.gui.Main'
|
mainClassName = 'com.lyndir.masterpassword.gui.MasterPassword'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
|
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p2'
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import com.lyndir.masterpassword.gui.util.Platform;
|
||||||
|
import com.lyndir.masterpassword.gui.util.Res;
|
||||||
|
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-28
|
||||||
|
*/
|
||||||
|
public class GUI {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.get( GUI.class );
|
||||||
|
|
||||||
|
private final MasterPasswordFrame frame = new MasterPasswordFrame();
|
||||||
|
|
||||||
|
public GUI() {
|
||||||
|
Platform.get().installAppForegroundHandler( this::open );
|
||||||
|
Platform.get().installAppReopenHandler( this::open );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void open() {
|
||||||
|
Res.ui( () -> frame.setVisible( true ) );
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,14 @@ 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.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.masterpassword.gui.platform.BaseGUI;
|
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
||||||
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.*;
|
import java.net.*;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
|
|
||||||
|
|
||||||
@ -35,33 +39,42 @@ import javax.swing.*;
|
|||||||
*
|
*
|
||||||
* @author mbillemo
|
* @author mbillemo
|
||||||
*/
|
*/
|
||||||
public final class Main {
|
public final class MasterPassword {
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( Main.class );
|
private static final Logger logger = Logger.get( MasterPassword.class );
|
||||||
|
|
||||||
public static void main(final String... args) {
|
private static final MasterPassword instance = new MasterPassword();
|
||||||
// Thread.setDefaultUncaughtExceptionHandler(
|
|
||||||
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
|
|
||||||
|
|
||||||
// Try and set the system look & feel, if available.
|
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
|
||||||
try {
|
|
||||||
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
|
||||||
}
|
|
||||||
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check online to see if this version has been superseded.
|
@Nullable
|
||||||
if (Config.get().checkForUpdates())
|
private MPUser<?> activeUser;
|
||||||
checkUpdate();
|
|
||||||
|
|
||||||
// Create a platform-specific GUI and open it.
|
public static MasterPassword get() {
|
||||||
BaseGUI.createPlatformGUI().open();
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean addListener(final Listener listener) {
|
||||||
|
return listeners.add( listener );
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean removeListener(final Listener listener) {
|
||||||
|
return listeners.remove( listener );
|
||||||
|
}
|
||||||
|
|
||||||
|
public void activateUser(final MPUser<?> user) {
|
||||||
|
if (ObjectUtils.equals( activeUser, user ))
|
||||||
|
return;
|
||||||
|
|
||||||
|
activeUser = user;
|
||||||
|
for (final Listener listener : listeners)
|
||||||
|
listener.onUserSelected( activeUser );
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkUpdate() {
|
private static void checkUpdate() {
|
||||||
try {
|
try {
|
||||||
String implementationVersion = Main.class.getPackage().getImplementationVersion();
|
String implementationVersion = MasterPassword.class.getPackage().getImplementationVersion();
|
||||||
String latestVersion = new ByteSource() {
|
String latestVersion = new ByteSource() {
|
||||||
@Override
|
@Override
|
||||||
public InputStream openStream()
|
public InputStream openStream()
|
||||||
@ -90,4 +103,27 @@ public final class Main {
|
|||||||
logger.wrn( e, "Couldn't check for version update." );
|
logger.wrn( e, "Couldn't check for version update." );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(final String... args) {
|
||||||
|
// Thread.setDefaultUncaughtExceptionHandler(
|
||||||
|
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
|
||||||
|
|
||||||
|
// Try and set the system look & feel, if available.
|
||||||
|
try {
|
||||||
|
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
|
||||||
|
}
|
||||||
|
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check online to see if this version has been superseded.
|
||||||
|
if (Config.get().checkForUpdates())
|
||||||
|
checkUpdate();
|
||||||
|
|
||||||
|
// Create a platform-specific GUI and open it.
|
||||||
|
new GUI().open();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
void onUserSelected(@Nullable MPUser<?> user);
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,62 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword.gui.platform;
|
|
||||||
|
|
||||||
import com.apple.eawt.*;
|
|
||||||
import com.google.common.base.Preconditions;
|
|
||||||
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 2014-06-10
|
|
||||||
*/
|
|
||||||
public class AppleGUI extends BaseGUI {
|
|
||||||
|
|
||||||
static Application application = Preconditions.checkNotNull(
|
|
||||||
Application.getApplication(), "Not an Apple Java application." );
|
|
||||||
|
|
||||||
public AppleGUI() {
|
|
||||||
application.addAppEventListener( new AppEventHandler() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected MasterPasswordFrame createFrame() {
|
|
||||||
MasterPasswordFrame frame = super.createFrame();
|
|
||||||
frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AppEventHandler implements AppForegroundListener, AppReOpenedListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appMovedToBackground(final AppEvent.AppForegroundEvent arg0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appRaisedToForeground(final AppEvent.AppForegroundEvent arg0) {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appReOpened(final AppEvent.AppReOpenedEvent arg0) {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,58 +0,0 @@
|
|||||||
package com.lyndir.masterpassword.gui.platform;
|
|
||||||
|
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
|
||||||
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
|
||||||
import com.lyndir.masterpassword.gui.util.Res;
|
|
||||||
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.Optional;
|
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 2018-07-28
|
|
||||||
*/
|
|
||||||
public class BaseGUI {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.get( BaseGUI.class );
|
|
||||||
|
|
||||||
private final MasterPasswordFrame frame = createFrame();
|
|
||||||
|
|
||||||
public static BaseGUI createPlatformGUI() {
|
|
||||||
BaseGUI jdk9GUI = construct( "com.lyndir.masterpassword.gui.platform.JDK9GUI" );
|
|
||||||
if (jdk9GUI != null)
|
|
||||||
return jdk9GUI;
|
|
||||||
|
|
||||||
BaseGUI appleGUI = construct( "com.lyndir.masterpassword.gui.platform.AppleGUI" );
|
|
||||||
if (appleGUI != null)
|
|
||||||
return appleGUI;
|
|
||||||
|
|
||||||
// Use platform-independent GUI.
|
|
||||||
return new BaseGUI();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static BaseGUI construct(final String typeName) {
|
|
||||||
try {
|
|
||||||
// AppleGUI adds support for macOS features.
|
|
||||||
Optional<Class<BaseGUI>> gui = TypeUtils.loadClass( typeName );
|
|
||||||
if (gui.isPresent())
|
|
||||||
return gui.get().getConstructor().newInstance();
|
|
||||||
}
|
|
||||||
catch (@SuppressWarnings("ErrorNotRethrown") final LinkageError ignored) {
|
|
||||||
}
|
|
||||||
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
|
|
||||||
throw logger.bug( e );
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MasterPasswordFrame createFrame() {
|
|
||||||
return new MasterPasswordFrame();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void open() {
|
|
||||||
Res.ui( () -> frame.setVisible( true ) );
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,60 +0,0 @@
|
|||||||
//==============================================================================
|
|
||||||
// This file is part of Master Password.
|
|
||||||
// Copyright (c) 2011-2017, Maarten Billemont.
|
|
||||||
//
|
|
||||||
// Master Password is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// Master Password is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU General Public License for more details.
|
|
||||||
//
|
|
||||||
// You can find a copy of the GNU General Public License in the
|
|
||||||
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
|
|
||||||
//==============================================================================
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword.gui.platform;
|
|
||||||
|
|
||||||
import com.lyndir.masterpassword.gui.view.MasterPasswordFrame;
|
|
||||||
import java.awt.*;
|
|
||||||
import java.awt.desktop.*;
|
|
||||||
import javax.swing.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 2014-06-10
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("Since15")
|
|
||||||
public class JDK9GUI extends BaseGUI {
|
|
||||||
|
|
||||||
public JDK9GUI() {
|
|
||||||
Desktop.getDesktop().addAppEventListener( new AppEventHandler() );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected MasterPasswordFrame createFrame() {
|
|
||||||
MasterPasswordFrame frame = super.createFrame();
|
|
||||||
frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
|
|
||||||
return frame;
|
|
||||||
}
|
|
||||||
|
|
||||||
private class AppEventHandler implements AppForegroundListener, AppReopenedListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appRaisedToForeground(final AppForegroundEvent e) {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appMovedToBackground(final AppForegroundEvent e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void appReopened(final AppReopenedEvent e) {
|
|
||||||
open();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,6 +21,7 @@ package com.lyndir.masterpassword.gui.util;
|
|||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ActionEvent;
|
import java.awt.event.ActionEvent;
|
||||||
import java.awt.event.ActionListener;
|
import java.awt.event.ActionListener;
|
||||||
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -110,6 +111,29 @@ public abstract class Components {
|
|||||||
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
|
return (option < 0)? JOptionPane.CLOSED_OPTION: option;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static File showLoadDialog(@Nullable final Component owner, final String title) {
|
||||||
|
return showFileDialog( owner, title, FileDialog.LOAD, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static File showSaveDialog(@Nullable final Component owner, final String title, final String fileName) {
|
||||||
|
return showFileDialog( owner, title, FileDialog.SAVE, fileName );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static File showFileDialog(@Nullable final Component owner, final String title,
|
||||||
|
final int mode, @Nullable final String fileName) {
|
||||||
|
FileDialog fileDialog = new FileDialog( JOptionPane.getFrameForComponent( owner ), title, mode );
|
||||||
|
fileDialog.setFile( fileName );
|
||||||
|
fileDialog.setLocationRelativeTo( owner );
|
||||||
|
fileDialog.setLocationByPlatform( true );
|
||||||
|
fileDialog.setVisible( true );
|
||||||
|
|
||||||
|
File[] selectedFiles = fileDialog.getFiles();
|
||||||
|
return ((selectedFiles != null) && (selectedFiles.length > 0))? selectedFiles[0]: null;
|
||||||
|
}
|
||||||
|
|
||||||
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) {
|
public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) {
|
||||||
JDialog dialog = new JDialog( (owner != null)? SwingUtilities.windowForComponent( owner ): null,
|
JDialog dialog = new JDialog( (owner != null)? SwingUtilities.windowForComponent( owner ): null,
|
||||||
title, Dialog.ModalityType.DOCUMENT_MODAL );
|
title, Dialog.ModalityType.DOCUMENT_MODAL );
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.util;
|
||||||
|
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import com.lyndir.lhunath.opal.system.util.TypeUtils;
|
||||||
|
import com.lyndir.masterpassword.gui.util.platform.BasePlatform;
|
||||||
|
import com.lyndir.masterpassword.gui.util.platform.IPlatform;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.util.Optional;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-29
|
||||||
|
*/
|
||||||
|
public final class Platform {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.get( Platform.class );
|
||||||
|
private static final IPlatform activePlatform;
|
||||||
|
|
||||||
|
static {
|
||||||
|
IPlatform tryPlatform;
|
||||||
|
if (null != (tryPlatform = construct( "com.lyndir.masterpassword.gui.util.platform.JDK9Platform" )))
|
||||||
|
activePlatform = tryPlatform;
|
||||||
|
|
||||||
|
else if (null != (tryPlatform = construct( "com.lyndir.masterpassword.gui.util.platform.ApplePlatform" )))
|
||||||
|
activePlatform = tryPlatform;
|
||||||
|
|
||||||
|
else
|
||||||
|
activePlatform = new BasePlatform();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static <T> T construct(final String typeName) {
|
||||||
|
try {
|
||||||
|
// AppleGUI adds support for macOS features.
|
||||||
|
Optional<Class<T>> gui = TypeUtils.loadClass( typeName );
|
||||||
|
if (gui.isPresent())
|
||||||
|
return gui.get().getConstructor().newInstance();
|
||||||
|
}
|
||||||
|
catch (@SuppressWarnings("ErrorNotRethrown") final LinkageError ignored) {
|
||||||
|
}
|
||||||
|
catch (final IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {
|
||||||
|
throw logger.bug( e );
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IPlatform get() {
|
||||||
|
return activePlatform;
|
||||||
|
}
|
||||||
|
}
|
@ -136,6 +136,14 @@ public abstract class Res {
|
|||||||
return icon( "media/icon_reset.png" );
|
return icon( "media/icon_reset.png" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Icon import_() {
|
||||||
|
return icon( "media/icon_import.png" );
|
||||||
|
}
|
||||||
|
|
||||||
|
public Icon export() {
|
||||||
|
return icon( "media/icon_export.png" );
|
||||||
|
}
|
||||||
|
|
||||||
public Icon settings() {
|
public Icon settings() {
|
||||||
return icon( "media/icon_settings.png" );
|
return icon( "media/icon_settings.png" );
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.util.platform;
|
||||||
|
|
||||||
|
import com.apple.eawt.*;
|
||||||
|
import com.apple.eio.FileManager;
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Throwables;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-29
|
||||||
|
*/
|
||||||
|
public class ApplePlatform implements IPlatform {
|
||||||
|
|
||||||
|
static Application application = Preconditions.checkNotNull(
|
||||||
|
Application.getApplication(), "Not an Apple Java application." );
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean installAppForegroundHandler(final Runnable handler) {
|
||||||
|
application.addAppEventListener( new AppForegroundListener() {
|
||||||
|
@Override
|
||||||
|
public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
|
||||||
|
handler.run();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean installAppReopenHandler(final Runnable handler) {
|
||||||
|
application.addAppEventListener( (AppReOpenedListener) e -> handler.run() );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean show(final File file) {
|
||||||
|
try {
|
||||||
|
return FileManager.revealInFinder( file );
|
||||||
|
}
|
||||||
|
catch (final FileNotFoundException ignored) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.util.platform;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-29
|
||||||
|
*/
|
||||||
|
public class BasePlatform implements IPlatform {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean installAppForegroundHandler(final Runnable handler) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean installAppReopenHandler(final Runnable handler) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean show(final File file) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.util.platform;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-29
|
||||||
|
*/
|
||||||
|
public interface IPlatform {
|
||||||
|
|
||||||
|
boolean installAppForegroundHandler(Runnable handler);
|
||||||
|
|
||||||
|
boolean installAppReopenHandler(Runnable handler);
|
||||||
|
|
||||||
|
boolean show(File file);
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.lyndir.masterpassword.gui.util.platform;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.desktop.*;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2018-07-29
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("Since15")
|
||||||
|
public class JDK9Platform implements IPlatform {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean installAppForegroundHandler(final Runnable handler) {
|
||||||
|
Desktop.getDesktop().addAppEventListener( new AppForegroundListener() {
|
||||||
|
@Override
|
||||||
|
public void appRaisedToForeground(final AppForegroundEvent e) {
|
||||||
|
handler.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void appMovedToBackground(final AppForegroundEvent e) {
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean installAppReopenHandler(final Runnable handler) {
|
||||||
|
Desktop.getDesktop().addAppEventListener( (AppReopenedListener) e -> handler.run() );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean show(final File file) {
|
||||||
|
if (!file.exists())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Desktop.getDesktop().browseFileDirectory( file );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,6 @@
|
|||||||
* @author lhunath, 2018-04-26
|
* @author lhunath, 2018-04-26
|
||||||
*/
|
*/
|
||||||
@ParametersAreNonnullByDefault
|
@ParametersAreNonnullByDefault
|
||||||
package com.lyndir.masterpassword.gui.platform;
|
package com.lyndir.masterpassword.gui.util.platform;
|
||||||
|
|
||||||
import javax.annotation.ParametersAreNonnullByDefault;
|
import javax.annotation.ParametersAreNonnullByDefault;
|
@ -3,6 +3,7 @@ package com.lyndir.masterpassword.gui.view;
|
|||||||
import static com.lyndir.masterpassword.util.Utilities.*;
|
import static com.lyndir.masterpassword.util.Utilities.*;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSortedSet;
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
|
import com.lyndir.masterpassword.gui.MasterPassword;
|
||||||
import com.lyndir.masterpassword.gui.util.*;
|
import com.lyndir.masterpassword.gui.util.*;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
import com.lyndir.masterpassword.model.impl.MPFileUser;
|
||||||
@ -18,15 +19,13 @@ import javax.swing.*;
|
|||||||
* @author lhunath, 2018-07-14
|
* @author lhunath, 2018-07-14
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
public class FilesPanel extends JPanel implements MPFileUserManager.Listener {
|
public class FilesPanel extends JPanel implements MPFileUserManager.Listener, MasterPassword.Listener {
|
||||||
|
|
||||||
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
|
|
||||||
|
|
||||||
private final JButton avatarButton = Components.button( Res.icons().avatar( 0 ), event -> setAvatar(),
|
private final JButton avatarButton = Components.button( Res.icons().avatar( 0 ), event -> setAvatar(),
|
||||||
"Click to change the user's avatar." );
|
"Click to change the user's avatar." );
|
||||||
|
|
||||||
private final CollectionListModel<MPUser<?>> usersModel =
|
private final CollectionListModel<MPUser<?>> usersModel =
|
||||||
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( this::setUser );
|
CollectionListModel.<MPUser<?>>copy( MPFileUserManager.get().getFiles() ).selection( MasterPassword.get()::activateUser );
|
||||||
private final JComboBox<? extends MPUser<?>> userField =
|
private final JComboBox<? extends MPUser<?>> userField =
|
||||||
Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) );
|
Components.comboBox( usersModel, user -> ifNotNull( user, MPUser::getFullName ) );
|
||||||
|
|
||||||
@ -50,10 +49,7 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener {
|
|||||||
add( userField );
|
add( userField );
|
||||||
|
|
||||||
MPFileUserManager.get().addListener( this );
|
MPFileUserManager.get().addListener( this );
|
||||||
}
|
MasterPassword.get().addListener( this );
|
||||||
|
|
||||||
public boolean addListener(final Listener listener) {
|
|
||||||
return listeners.add( listener );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAvatar() {
|
private void setAvatar() {
|
||||||
@ -65,20 +61,14 @@ public class FilesPanel extends JPanel implements MPFileUserManager.Listener {
|
|||||||
avatarButton.setIcon( Res.icons().avatar( selectedUser.getAvatar() ) );
|
avatarButton.setIcon( Res.icons().avatar( selectedUser.getAvatar() ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUser(@Nullable final MPUser<?> user) {
|
|
||||||
avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
|
|
||||||
|
|
||||||
for (final Listener listener : listeners)
|
|
||||||
listener.onUserSelected( user );
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFilesUpdated(final ImmutableSortedSet<MPFileUser> files) {
|
public void onFilesUpdated(final ImmutableSortedSet<MPFileUser> files) {
|
||||||
usersModel.set( files );
|
usersModel.set( files );
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface Listener {
|
@Override
|
||||||
|
public void onUserSelected(@Nullable final MPUser<?> user) {
|
||||||
void onUserSelected(@Nullable MPUser<?> user);
|
usersModel.setSelectedItem( user );
|
||||||
|
avatarButton.setIcon( Res.icons().avatar( (user == null)? 0: user.getAvatar() ) );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,12 @@ package com.lyndir.masterpassword.gui.view;
|
|||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.masterpassword.gui.util.Components;
|
import com.lyndir.masterpassword.gui.util.Components;
|
||||||
import com.lyndir.masterpassword.gui.util.Res;
|
import com.lyndir.masterpassword.gui.util.Res;
|
||||||
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
import com.lyndir.masterpassword.model.impl.MPFileUserManager;
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.event.ComponentAdapter;
|
import java.awt.event.ComponentAdapter;
|
||||||
import java.awt.event.ComponentEvent;
|
import java.awt.event.ComponentEvent;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.border.BevelBorder;
|
import javax.swing.border.BevelBorder;
|
||||||
|
|
||||||
@ -40,8 +42,6 @@ public class MasterPasswordFrame extends JFrame {
|
|||||||
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
|
BorderFactory.createBevelBorder( BevelBorder.RAISED, Res.colors().controlBorder(), Res.colors().frameBg() ),
|
||||||
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
|
Res.colors().controlBg(), BoxLayout.PAGE_AXIS, userContent ), BorderLayout.CENTER );
|
||||||
|
|
||||||
filesPanel.addListener( userContent );
|
|
||||||
|
|
||||||
addComponentListener( new ComponentHandler() );
|
addComponentListener( new ComponentHandler() );
|
||||||
setPreferredSize( new Dimension( 800, 560 ) );
|
setPreferredSize( new Dimension( 800, 560 ) );
|
||||||
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
setDefaultCloseOperation( DISPOSE_ON_CLOSE );
|
||||||
|
@ -8,15 +8,22 @@ import com.google.common.primitives.UnsignedInteger;
|
|||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
|
import com.lyndir.masterpassword.gui.MasterPassword;
|
||||||
import com.lyndir.masterpassword.gui.model.MPNewSite;
|
import com.lyndir.masterpassword.gui.model.MPNewSite;
|
||||||
import com.lyndir.masterpassword.gui.util.*;
|
import com.lyndir.masterpassword.gui.util.*;
|
||||||
|
import com.lyndir.masterpassword.gui.util.Platform;
|
||||||
import com.lyndir.masterpassword.model.*;
|
import com.lyndir.masterpassword.model.*;
|
||||||
import com.lyndir.masterpassword.model.impl.*;
|
import com.lyndir.masterpassword.model.impl.*;
|
||||||
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.*;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
@ -31,20 +38,22 @@ import javax.swing.event.DocumentListener;
|
|||||||
* @author lhunath, 2018-07-14
|
* @author lhunath, 2018-07-14
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("SerializableStoresNonSerializable")
|
@SuppressWarnings("SerializableStoresNonSerializable")
|
||||||
public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPUser.Listener {
|
public class UserContentPanel extends JPanel implements MasterPassword.Listener, MPUser.Listener {
|
||||||
|
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private static final Logger logger = Logger.get( UserContentPanel.class );
|
private static final Logger logger = Logger.get( UserContentPanel.class );
|
||||||
private static final JButton iconButton = Components.button( Res.icons().user(), null, null );
|
private static final JButton iconButton = Components.button( Res.icons().user(), null, null );
|
||||||
|
|
||||||
private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
|
private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(),
|
||||||
"Add a new user to Master Password." );
|
"Add a new user to Master Password." );
|
||||||
|
private final JButton importButton = Components.button( Res.icons().import_(), event -> importUser(),
|
||||||
|
"Import a user from a backup file into Master Password." );
|
||||||
|
|
||||||
private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
|
private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS );
|
||||||
private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
|
private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS );
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private MPUser<?> activeUser;
|
private MPUser<?> showingUser;
|
||||||
private ContentMode contentMode;
|
private ContentMode contentMode;
|
||||||
|
|
||||||
public UserContentPanel() {
|
public UserContentPanel() {
|
||||||
@ -53,7 +62,9 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
|
|
||||||
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
||||||
setBorder( Components.marginBorder() );
|
setBorder( Components.marginBorder() );
|
||||||
setUser( null );
|
showUser( null );
|
||||||
|
|
||||||
|
MasterPassword.get().addListener( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected JComponent getUserToolbar() {
|
protected JComponent getUserToolbar() {
|
||||||
@ -66,52 +77,52 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserSelected(@Nullable final MPUser<?> user) {
|
public void onUserSelected(@Nullable final MPUser<?> user) {
|
||||||
setUser( user );
|
showUser( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserUpdated(final MPUser<?> user) {
|
public void onUserUpdated(final MPUser<?> user) {
|
||||||
setUser( user );
|
showUser( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserAuthenticated(final MPUser<?> user) {
|
public void onUserAuthenticated(final MPUser<?> user) {
|
||||||
setUser( user );
|
showUser( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserInvalidated(final MPUser<?> user) {
|
public void onUserInvalidated(final MPUser<?> user) {
|
||||||
setUser( user );
|
showUser( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUser(@Nullable final MPUser<?> user) {
|
private void showUser(@Nullable final MPUser<?> user) {
|
||||||
Res.ui( () -> {
|
Res.ui( () -> {
|
||||||
if (activeUser != null)
|
if (showingUser != null)
|
||||||
activeUser.removeListener( this );
|
showingUser.removeListener( this );
|
||||||
|
|
||||||
ContentMode newContentMode = ContentMode.getContentMode( user );
|
ContentMode newContentMode = ContentMode.getContentMode( user );
|
||||||
if ((newContentMode != contentMode) || !ObjectUtils.equals( activeUser, user )) {
|
if ((newContentMode != contentMode) || !ObjectUtils.equals( showingUser, user )) {
|
||||||
userToolbar.removeAll();
|
userToolbar.removeAll();
|
||||||
siteToolbar.removeAll();
|
siteToolbar.removeAll();
|
||||||
removeAll();
|
removeAll();
|
||||||
activeUser = user;
|
showingUser = user;
|
||||||
switch (contentMode = newContentMode) {
|
switch (contentMode = newContentMode) {
|
||||||
case NO_USER:
|
case NO_USER:
|
||||||
add( new NoUserPanel() );
|
add( new NoUserPanel() );
|
||||||
break;
|
break;
|
||||||
case AUTHENTICATE:
|
case AUTHENTICATE:
|
||||||
add( new AuthenticateUserPanel( Preconditions.checkNotNull( activeUser ) ) );
|
add( new AuthenticateUserPanel( Preconditions.checkNotNull( showingUser ) ) );
|
||||||
break;
|
break;
|
||||||
case AUTHENTICATED:
|
case AUTHENTICATED:
|
||||||
add( new AuthenticatedUserPanel( Preconditions.checkNotNull( activeUser ) ) );
|
add( new AuthenticatedUserPanel( Preconditions.checkNotNull( showingUser ) ) );
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
revalidate();
|
revalidate();
|
||||||
transferFocus();
|
transferFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeUser != null)
|
if (showingUser != null)
|
||||||
activeUser.addListener( this );
|
showingUser.addListener( this );
|
||||||
} );
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +133,70 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
if (fullName == null)
|
if (fullName == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
setUser( MPFileUserManager.get().add( fullName.toString() ) );
|
MasterPassword.get().activateUser( MPFileUserManager.get().add( fullName.toString() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
private void importUser() {
|
||||||
|
File importFile = Components.showLoadDialog( this, "Import User File" );
|
||||||
|
if (importFile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
MPFileUser importUser = MPFileUser.load( importFile );
|
||||||
|
if (importUser == null) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
this, "Not a Master Password file.",
|
||||||
|
"Import Failed", JOptionPane.ERROR_MESSAGE );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JPasswordField passwordField = Components.passwordField();
|
||||||
|
if (JOptionPane.OK_OPTION == Components.showDialog( this, "Import User", new JOptionPane( Components.panel(
|
||||||
|
BoxLayout.PAGE_AXIS,
|
||||||
|
Components.label( strf( "<html>Enter the master password to import <strong>%s</strong>:</html>",
|
||||||
|
importUser.getFullName() ) ),
|
||||||
|
Components.strut(),
|
||||||
|
passwordField ), JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) {
|
||||||
|
@Override
|
||||||
|
public void selectInitialValue() {
|
||||||
|
passwordField.requestFocusInWindow();
|
||||||
|
}
|
||||||
|
} )) {
|
||||||
|
try {
|
||||||
|
importUser.authenticate( passwordField.getPassword() );
|
||||||
|
Optional<MPFileUser> existingUser = MPFileUserManager.get().getFiles().stream().filter(
|
||||||
|
user -> user.getFullName().equalsIgnoreCase( importUser.getFullName() ) ).findFirst();
|
||||||
|
if (existingUser.isPresent() && (JOptionPane.YES_OPTION != JOptionPane.showConfirmDialog(
|
||||||
|
this,
|
||||||
|
strf( "<html>Importing user <strong>%s</strong> from this file will replace the existing user with the imported one.<br>"
|
||||||
|
+ "Are you sure?<br><br>"
|
||||||
|
+ "<em>Existing user last modified: %s<br>Imported user last modified: %s</em></html>",
|
||||||
|
importUser.getFullName(),
|
||||||
|
Res.format( existingUser.get().getLastUsed() ),
|
||||||
|
Res.format( importUser.getLastUsed() ) ) )))
|
||||||
|
return;
|
||||||
|
|
||||||
|
MasterPassword.get().activateUser( MPFileUserManager.get().add( importUser ) );
|
||||||
|
}
|
||||||
|
catch (final MPIncorrectMasterPasswordException | MPAlgorithmException e) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
this, e.getLocalizedMessage(),
|
||||||
|
"Import Failed", JOptionPane.ERROR_MESSAGE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
logger.err( e, "While reading user import file." );
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
this, strf( "<html>Couldn't read import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
|
||||||
|
"Import Failed", JOptionPane.ERROR_MESSAGE );
|
||||||
|
}
|
||||||
|
catch (MPMarshalException e) {
|
||||||
|
logger.err( e, "While parsing user import file." );
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
this, strf( "<html>Couldn't parse import file:<br><pre>%s</pre></html>.", e.getLocalizedMessage() ),
|
||||||
|
"Import Failed", JOptionPane.ERROR_MESSAGE );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum ContentMode {
|
private enum ContentMode {
|
||||||
@ -147,6 +221,7 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) );
|
||||||
|
|
||||||
userToolbar.add( addButton );
|
userToolbar.add( addButton );
|
||||||
|
userToolbar.add( importButton );
|
||||||
|
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
add( Components.heading( "Select a user to proceed." ) );
|
add( Components.heading( "Select a user to proceed." ) );
|
||||||
@ -160,6 +235,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
private final MPUser<?> user;
|
private final MPUser<?> user;
|
||||||
|
|
||||||
|
private final JButton exportButton = Components.button( Res.icons().export(), event -> exportUser(),
|
||||||
|
"Export this user to a backup file." );
|
||||||
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(),
|
private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(),
|
||||||
"Delete this user from Master Password." );
|
"Delete this user from Master Password." );
|
||||||
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
|
private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(),
|
||||||
@ -177,6 +254,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
userToolbar.add( addButton );
|
userToolbar.add( addButton );
|
||||||
|
userToolbar.add( importButton );
|
||||||
|
userToolbar.add( exportButton );
|
||||||
userToolbar.add( deleteButton );
|
userToolbar.add( deleteButton );
|
||||||
userToolbar.add( resetButton );
|
userToolbar.add( resetButton );
|
||||||
|
|
||||||
@ -197,6 +276,27 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void exportUser() {
|
||||||
|
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
|
||||||
|
if (fileUser == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
File exportFile = Components.showSaveDialog( this, "Export User File", fileUser.getFile().getName() );
|
||||||
|
if (exportFile == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
Platform.get().show(
|
||||||
|
Files.copy( fileUser.getFile().toPath(), exportFile.toPath(),
|
||||||
|
StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.COPY_ATTRIBUTES ).toFile() );
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
JOptionPane.showMessageDialog(
|
||||||
|
this, e.getLocalizedMessage(),
|
||||||
|
"Export Failed", JOptionPane.ERROR_MESSAGE );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void deleteUser() {
|
private void deleteUser() {
|
||||||
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
|
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null;
|
||||||
if (fileUser == null)
|
if (fileUser == null)
|
||||||
@ -372,7 +472,17 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU
|
|||||||
sitesModel.registerList( sitesList );
|
sitesModel.registerList( sitesList );
|
||||||
add( Box.createGlue() );
|
add( Box.createGlue() );
|
||||||
|
|
||||||
user.addListener( this );
|
addComponentListener( new ComponentAdapter() {
|
||||||
|
@Override
|
||||||
|
public void componentShown(final ComponentEvent e) {
|
||||||
|
user.addListener( AuthenticatedUserPanel.this );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void componentHidden(final ComponentEvent e) {
|
||||||
|
user.removeListener( AuthenticatedUserPanel.this );
|
||||||
|
}
|
||||||
|
} );
|
||||||
}
|
}
|
||||||
|
|
||||||
public void showUserPreferences() {
|
public void showUserPreferences() {
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
@ -55,12 +55,11 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
|||||||
/**
|
/**
|
||||||
* Performs an authentication attempt against the keyID for this user.
|
* Performs an authentication attempt against the keyID for this user.
|
||||||
*
|
*
|
||||||
* Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
|
|
||||||
*
|
|
||||||
* @param masterPassword The password to authenticate with.
|
* @param masterPassword The password to authenticate with.
|
||||||
* You cannot re-use this array after passing it in, authentication will destroy its contents.
|
|
||||||
*
|
*
|
||||||
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
||||||
|
* @apiNote If a keyID is not set, authentication will always succeed and the keyID will be set to match the given master password.
|
||||||
|
* <b>This method destroys the contents of the {@code masterPassword} array.</b>
|
||||||
*/
|
*/
|
||||||
void authenticate(char[] masterPassword)
|
void authenticate(char[] masterPassword)
|
||||||
throws MPIncorrectMasterPasswordException, MPAlgorithmException;
|
throws MPIncorrectMasterPasswordException, MPAlgorithmException;
|
||||||
@ -68,11 +67,10 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
|||||||
/**
|
/**
|
||||||
* Performs an authentication attempt against the keyID for this user.
|
* Performs an authentication attempt against the keyID for this user.
|
||||||
*
|
*
|
||||||
* Note: If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
|
|
||||||
*
|
|
||||||
* @param masterKey The master key to authenticate with.
|
* @param masterKey The master key to authenticate with.
|
||||||
*
|
*
|
||||||
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
|
||||||
|
* @apiNote If a keyID is not set, authentication will always succeed and the keyID will be set to match the given key.
|
||||||
*/
|
*/
|
||||||
void authenticate(MPMasterKey masterKey)
|
void authenticate(MPMasterKey masterKey)
|
||||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||||
|
@ -37,8 +37,9 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeable implements MPUser<S> {
|
public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeable implements MPUser<S> {
|
||||||
|
|
||||||
protected final Logger logger = Logger.get( getClass() );
|
private static final Logger logger = Logger.get( MPBasicUser.class );
|
||||||
private final Set<Listener> listeners = new CopyOnWriteArraySet<>();
|
|
||||||
|
private final Set<Listener> listeners = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
private int avatar;
|
private int avatar;
|
||||||
private final String fullName;
|
private final String fullName;
|
||||||
@ -65,7 +66,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAvatar(final int avatar) {
|
public void setAvatar(final int avatar) {
|
||||||
if (Objects.equals(this.avatar, avatar))
|
if (Objects.equals( this.avatar, avatar ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
@ -86,7 +87,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?, ?>> extends Changeabl
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||||
if (Objects.equals(this.algorithm, algorithm))
|
if (Objects.equals( this.algorithm, algorithm ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.algorithm = algorithm;
|
this.algorithm = algorithm;
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
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;
|
||||||
@ -36,6 +37,8 @@ import org.joda.time.ReadableInstant;
|
|||||||
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
@SuppressWarnings("ComparableImplementedButEqualsNotOverridden")
|
||||||
public class MPFileUser extends MPBasicUser<MPFileSite> {
|
public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.get( MPFileUser.class );
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private byte[] keyID;
|
private byte[] keyID;
|
||||||
private File path;
|
private File path;
|
||||||
@ -46,13 +49,23 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
private ReadableInstant lastUsed;
|
private ReadableInstant lastUsed;
|
||||||
private boolean complete;
|
private boolean complete;
|
||||||
|
|
||||||
public MPFileUser(final String fullName) {
|
@Nullable
|
||||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
public static MPFileUser load(final File file)
|
||||||
|
throws IOException, MPMarshalException {
|
||||||
|
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||||
|
if (file.getName().endsWith( format.fileSuffix() ))
|
||||||
|
return format.unmarshaller().readUser( file );
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm) {
|
public MPFileUser(final String fullName, final File path) {
|
||||||
|
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), path );
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File path) {
|
||||||
this( fullName, keyID, algorithm, 0, null, new Instant(),
|
this( fullName, keyID, algorithm, 0, null, new Instant(),
|
||||||
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, MPFileUserManager.get().getPath() );
|
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, path );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
|
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
|
||||||
@ -74,6 +87,10 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
return (keyID == null)? null: keyID.clone();
|
return (keyID == null)? null: keyID.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setPath(final File path) {
|
||||||
|
this.path = path;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setAlgorithm(final MPAlgorithm algorithm) {
|
public void setAlgorithm(final MPAlgorithm algorithm) {
|
||||||
if (!algorithm.equals( getAlgorithm() ) && (keyID != null)) {
|
if (!algorithm.equals( getAlgorithm() ) && (keyID != null)) {
|
||||||
@ -99,7 +116,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setFormat(final MPMarshalFormat format) {
|
public void setFormat(final MPMarshalFormat format) {
|
||||||
if (Objects.equals(this.format, format))
|
if (Objects.equals( this.format, format ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.format = format;
|
this.format = format;
|
||||||
@ -111,7 +128,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
|
public void setContentMode(final MPMarshaller.ContentMode contentMode) {
|
||||||
if (Objects.equals(this.contentMode, contentMode))
|
if (Objects.equals( this.contentMode, contentMode ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.contentMode = contentMode;
|
this.contentMode = contentMode;
|
||||||
@ -123,7 +140,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setDefaultType(final MPResultType defaultType) {
|
public void setDefaultType(final MPResultType defaultType) {
|
||||||
if (Objects.equals(this.defaultType, defaultType))
|
if (Objects.equals( this.defaultType, defaultType ))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
this.defaultType = defaultType;
|
this.defaultType = defaultType;
|
||||||
@ -169,6 +186,19 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
try {
|
||||||
|
if (isComplete())
|
||||||
|
getFormat().marshaller().marshall( this );
|
||||||
|
}
|
||||||
|
catch (final MPKeyUnavailableException e) {
|
||||||
|
logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
|
||||||
|
}
|
||||||
|
catch (final IOException | MPMarshalException | MPAlgorithmException e) {
|
||||||
|
logger.err( e, "Unable to write out changes for user: %s", this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
keyID = null;
|
keyID = null;
|
||||||
@ -183,16 +213,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onChanged() {
|
protected void onChanged() {
|
||||||
try {
|
save();
|
||||||
if (isComplete())
|
|
||||||
getFormat().marshaller().marshall( this );
|
|
||||||
}
|
|
||||||
catch (final MPKeyUnavailableException e) {
|
|
||||||
logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
|
|
||||||
}
|
|
||||||
catch (final IOException | MPMarshalException | MPAlgorithmException e) {
|
|
||||||
logger.err( e, "Unable to write out changes for user: %s", this );
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onChanged();
|
super.onChanged();
|
||||||
}
|
}
|
||||||
|
@ -78,31 +78,40 @@ public class MPFileUserManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (final File file : pathFiles)
|
for (final File file : pathFiles)
|
||||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
try {
|
||||||
if (file.getName().endsWith( format.fileSuffix() ))
|
MPFileUser user = MPFileUser.load( file );
|
||||||
try {
|
if (user != null) {
|
||||||
MPFileUser user = format.unmarshaller().readUser( file );
|
MPFileUser previousUser = userByName.put( user.getFullName(), user );
|
||||||
MPFileUser previousUser = userByName.put( user.getFullName(), user );
|
if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
|
||||||
if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
|
userByName.put( previousUser.getFullName(), previousUser );
|
||||||
userByName.put( previousUser.getFullName(), previousUser );
|
}
|
||||||
break;
|
}
|
||||||
}
|
catch (final IOException | MPMarshalException e) {
|
||||||
catch (final IOException | MPMarshalException e) {
|
logger.err( e, "Couldn't read user from: %s", file );
|
||||||
logger.err( e, "Couldn't read user from: %s", file );
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fireUpdated();
|
fireUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser add(final String fullName) {
|
public MPFileUser add(final String fullName) {
|
||||||
MPFileUser user = new MPFileUser( fullName );
|
return add( new MPFileUser( fullName, getPath() ) );
|
||||||
userByName.put( user.getFullName(), user );
|
}
|
||||||
|
|
||||||
|
public MPFileUser add(final MPFileUser user) {
|
||||||
|
user.setPath( getPath() );
|
||||||
|
user.save();
|
||||||
|
|
||||||
|
MPFileUser oldUser = userByName.put( user.getFullName(), user );
|
||||||
|
if (oldUser != null)
|
||||||
|
oldUser.invalidate();
|
||||||
fireUpdated();
|
fireUpdated();
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void delete(final MPFileUser user) {
|
public void delete(final MPFileUser user) {
|
||||||
|
user.invalidate();
|
||||||
|
|
||||||
// Remove deleted users.
|
// Remove deleted users.
|
||||||
File userFile = user.getFile();
|
File userFile = user.getFile();
|
||||||
if (userFile.exists() && !userFile.delete())
|
if (userFile.exists() && !userFile.delete())
|
||||||
|
Loading…
Reference in New Issue
Block a user