Commit 39dacc8e authored by Maarten Billemont's avatar Maarten Billemont

User config in _ext_mpw, global config.json & residence config.

Moved user preferences (default type & hide passwords) into _ext_mpw.
Fixed an issue with JSON serialization of any values.
Made update check & background residency globally configurable
preferences saved in config.json.
parent 34042e54
Pipeline #32950616 failed with stage
in 7 minutes and 43 seconds
...@@ -147,7 +147,7 @@ public class MPMasterKey { ...@@ -147,7 +147,7 @@ public class MPMasterKey {
* @return {@code null} if the result type is missing a required parameter. * @return {@code null} if the result type is missing a required parameter.
* *
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
* @throws MPAlgorithmException An internal system or algorithm error has occurred. * @throws MPAlgorithmException An internal system or algorithm error has occurred.
*/ */
@Nullable @Nullable
public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, public String siteResult(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
...@@ -185,7 +185,7 @@ public class MPMasterKey { ...@@ -185,7 +185,7 @@ public class MPMasterKey {
* {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}. * {@link #siteResult(String, MPAlgorithm, UnsignedInteger, MPKeyPurpose, String, MPResultType, String)}.
* *
* @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object. * @throws MPKeyUnavailableException {@link #invalidate()} has been called on this object.
* @throws MPAlgorithmException An internal system or algorithm error has occurred. * @throws MPAlgorithmException An internal system or algorithm error has occurred.
*/ */
@Nonnull @Nonnull
public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter, public String siteState(final String siteName, final MPAlgorithm algorithm, final UnsignedInteger siteCounter,
......
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
package com.lyndir.masterpassword.gui; package com.lyndir.masterpassword.gui;
import com.lyndir.lhunath.opal.system.util.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.masterpassword.model.MPConfig;
import com.lyndir.masterpassword.model.MPModelConstants; import com.lyndir.masterpassword.model.MPModelConstants;
...@@ -26,15 +27,32 @@ import com.lyndir.masterpassword.model.MPModelConstants; ...@@ -26,15 +27,32 @@ import com.lyndir.masterpassword.model.MPModelConstants;
* @author lhunath, 2014-08-31 * @author lhunath, 2014-08-31
*/ */
@SuppressWarnings("CallToSystemGetenv") @SuppressWarnings("CallToSystemGetenv")
public class MPConfig { public class MPGuiConfig extends MPConfig {
private static final MPConfig instance = new MPConfig(); public static MPGuiConfig get() {
return get( MPGuiConfig.class );
public static MPConfig get() {
return instance;
} }
Boolean checkForUpdates;
Boolean stayResident;
public boolean checkForUpdates() { public boolean checkForUpdates() {
return ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true ); return (checkForUpdates != null)? checkForUpdates:
ConversionUtils.toBoolean( System.getenv( MPModelConstants.env_checkUpdates ) ).orElse( true );
}
public void setCheckForUpdates(final boolean checkForUpdates) {
this.checkForUpdates = checkForUpdates;
MasterPassword.get().updateCheck();
setChanged();
}
public boolean stayResident() {
return (stayResident != null)? stayResident: false;
}
public void setStayResident(final boolean stayResident) {
this.stayResident = stayResident;
setChanged();
} }
} }
...@@ -46,10 +46,10 @@ import javax.swing.*; ...@@ -46,10 +46,10 @@ import javax.swing.*;
public final class MasterPassword { public final class MasterPassword {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterPassword.class ); private static final Logger logger = Logger.get( MasterPassword.class );
private static final MasterPassword instance = new MasterPassword(); private static final MasterPassword instance = new MasterPassword();
private final Provider keyMaster = Provider.getCurrentProvider( true );
private final Collection<Listener> listeners = new CopyOnWriteArraySet<>(); private final Collection<Listener> listeners = new CopyOnWriteArraySet<>();
@Nullable @Nullable
...@@ -97,7 +97,29 @@ public final class MasterPassword { ...@@ -97,7 +97,29 @@ public final class MasterPassword {
} ); } );
} }
public void checkUpdate() { public static void main(final String... args) {
//Thread.setDefaultUncaughtExceptionHandler(
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) );
// Set the system look & feel, if available.
try {
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
}
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
// Create and open the UI.
get().open();
// UI features.
get().updateResidence();
get().updateCheck();
}
public void updateCheck() {
if (!MPGuiConfig.get().checkForUpdates())
return;
try { try {
String implementationVersion = version(); String implementationVersion = version();
String latestVersion = new ByteSource() { String latestVersion = new ByteSource() {
...@@ -127,26 +149,10 @@ public final class MasterPassword { ...@@ -127,26 +149,10 @@ public final class MasterPassword {
} }
} }
public static void main(final String... args) { public void updateResidence() {
//Thread.setDefaultUncaughtExceptionHandler( Platform.get().installAppForegroundHandler( get()::open );
// (t, e) -> logger.bug( e, "Uncaught: %s", e.getLocalizedMessage() ) ); Platform.get().installAppReopenHandler( get()::open );
keyMaster.register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
// Try and set the system look & feel, if available.
try {
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
Platform.get().installAppForegroundHandler( get()::open );
Platform.get().installAppReopenHandler( get()::open );
Provider.getCurrentProvider( true ).register( MPGuiConstants.ui_hotkey, hotKey -> get().open() );
}
catch (final UnsupportedLookAndFeelException | ClassNotFoundException | InstantiationException | IllegalAccessException ignored) {
}
// Create a platform-specific GUI and open it.
get().open();
// Check online to see if this version has been superseded.
if (MPConfig.get().checkForUpdates())
get().checkUpdate();
} }
@SuppressWarnings("InterfaceMayBeAnnotatedFunctional") @SuppressWarnings("InterfaceMayBeAnnotatedFunctional")
......
...@@ -17,24 +17,47 @@ public class ApplePlatform implements IPlatform { ...@@ -17,24 +17,47 @@ public class ApplePlatform implements IPlatform {
private static final Application application = Preconditions.checkNotNull( private static final Application application = Preconditions.checkNotNull(
Application.getApplication(), "Not an Apple Java application." ); Application.getApplication(), "Not an Apple Java application." );
private AppForegroundListener appForegroundHandler;
private AppReOpenedListener appReopenHandler;
@Override @Override
public boolean installAppForegroundHandler(final Runnable handler) { public boolean installAppForegroundHandler(final Runnable handler) {
application.addAppEventListener( new AppForegroundListener() { if (appForegroundHandler == null)
@Override application.addAppEventListener( appForegroundHandler = new AppForegroundListener() {
public void appMovedToBackground(final AppEvent.AppForegroundEvent e) { @Override
} public void appMovedToBackground(final AppEvent.AppForegroundEvent e) {
}
@Override
public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) { @Override
handler.run(); public void appRaisedToForeground(final AppEvent.AppForegroundEvent e) {
} handler.run();
} ); }
} );
return true;
}
@Override
public boolean removeAppForegroundHandler() {
if (appForegroundHandler == null)
return false;
application.removeAppEventListener( appForegroundHandler );
return true; return true;
} }
@Override @Override
public boolean installAppReopenHandler(final Runnable handler) { public boolean installAppReopenHandler(final Runnable handler) {
application.addAppEventListener( (AppReOpenedListener) e -> handler.run() ); application.addAppEventListener( appReopenHandler = e -> handler.run() );
return true;
}
@Override
public boolean removeAppReopenHandler() {
if (appReopenHandler == null)
return false;
application.removeAppEventListener( appReopenHandler );
return true; return true;
} }
......
...@@ -14,11 +14,21 @@ public class BasePlatform implements IPlatform { ...@@ -14,11 +14,21 @@ public class BasePlatform implements IPlatform {
return false; return false;
} }
@Override
public boolean removeAppForegroundHandler() {
return false;
}
@Override @Override
public boolean installAppReopenHandler(final Runnable handler) { public boolean installAppReopenHandler(final Runnable handler) {
return false; return false;
} }
@Override
public boolean removeAppReopenHandler() {
return false;
}
@Override @Override
public boolean requestForeground() { public boolean requestForeground() {
return false; return false;
......
...@@ -12,8 +12,12 @@ public interface IPlatform { ...@@ -12,8 +12,12 @@ public interface IPlatform {
boolean installAppForegroundHandler(Runnable handler); boolean installAppForegroundHandler(Runnable handler);
boolean removeAppForegroundHandler();
boolean installAppReopenHandler(Runnable handler); boolean installAppReopenHandler(Runnable handler);
boolean removeAppReopenHandler();
boolean requestForeground(); boolean requestForeground();
boolean show(File file); boolean show(File file);
......
...@@ -17,24 +17,49 @@ public class JDK9Platform implements IPlatform { ...@@ -17,24 +17,49 @@ public class JDK9Platform implements IPlatform {
private static final Logger logger = Logger.get( JDK9Platform.class ); private static final Logger logger = Logger.get( JDK9Platform.class );
private static final Desktop desktop = Desktop.getDesktop(); private static final Desktop desktop = Desktop.getDesktop();
private AppForegroundListener appForegroundHandler;
private AppReopenedListener appReopenHandler;
@Override @Override
public boolean installAppForegroundHandler(final Runnable handler) { public boolean installAppForegroundHandler(final Runnable handler) {
desktop.addAppEventListener( new AppForegroundListener() { if (appForegroundHandler == null)
@Override desktop.addAppEventListener( appForegroundHandler = new AppForegroundListener() {
public void appRaisedToForeground(final AppForegroundEvent e) { @Override
handler.run(); public void appRaisedToForeground(final AppForegroundEvent e) {
} handler.run();
}
@Override
public void appMovedToBackground(final AppForegroundEvent e) { @Override
} public void appMovedToBackground(final AppForegroundEvent e) {
} ); }
} );
return true;
}
@Override
public boolean removeAppForegroundHandler() {
if (appForegroundHandler == null)
return false;
desktop.removeAppEventListener( appForegroundHandler );
return true; return true;
} }
@Override @Override
public boolean installAppReopenHandler(final Runnable handler) { public boolean installAppReopenHandler(final Runnable handler) {
desktop.addAppEventListener( (AppReopenedListener) e -> handler.run() ); if (appReopenHandler == null)
desktop.addAppEventListener( appReopenHandler = e -> handler.run() );
return true;
}
@Override
public boolean removeAppReopenHandler() {
if (appReopenHandler == null)
return false;
desktop.removeAppEventListener( appReopenHandler );
return true; return true;
} }
......
package com.lyndir.masterpassword.gui.view; 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.MPGuiConfig;
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.impl.MPFileUserManager; import com.lyndir.masterpassword.model.impl.MPFileUserManager;
import java.awt.*; import java.awt.*;
import java.awt.event.ComponentAdapter; import java.awt.event.*;
import java.awt.event.ComponentEvent;
import javax.swing.*; import javax.swing.*;
import javax.swing.border.BevelBorder; import javax.swing.border.BevelBorder;
...@@ -19,7 +19,7 @@ public class MasterPasswordFrame extends JFrame { ...@@ -19,7 +19,7 @@ public class MasterPasswordFrame extends JFrame {
private static final Logger logger = Logger.get( MasterPasswordFrame.class ); private static final Logger logger = Logger.get( MasterPasswordFrame.class );
private final UserContentPanel userContent; private final UserContentPanel userContent;
@SuppressWarnings("MagicNumber") @SuppressWarnings("MagicNumber")
public MasterPasswordFrame() { public MasterPasswordFrame() {
...@@ -39,6 +39,7 @@ public class MasterPasswordFrame extends JFrame { ...@@ -39,6 +39,7 @@ public class MasterPasswordFrame extends JFrame {
userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END ); userPanel.add( userContent.getSiteToolbar(), BorderLayout.LINE_END );
addComponentListener( new ComponentHandler() ); addComponentListener( new ComponentHandler() );
addWindowListener( new WindowHandler() );
setPreferredSize( new Dimension( 800, 560 ) ); setPreferredSize( new Dimension( 800, 560 ) );
setDefaultCloseOperation( DISPOSE_ON_CLOSE ); setDefaultCloseOperation( DISPOSE_ON_CLOSE );
pack(); pack();
...@@ -55,4 +56,14 @@ public class MasterPasswordFrame extends JFrame { ...@@ -55,4 +56,14 @@ public class MasterPasswordFrame extends JFrame {
userContent.transferFocus(); userContent.transferFocus();
} }
} }
private static class WindowHandler extends WindowAdapter {
@Override
public void windowClosed(final WindowEvent e) {
if (!MPGuiConfig.get().stayResident())
System.exit( 0 );
}
}
} }
...@@ -9,8 +9,7 @@ import com.google.common.util.concurrent.ListenableFuture; ...@@ -9,8 +9,7 @@ import com.google.common.util.concurrent.ListenableFuture;
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.MPGuiConstants; import com.lyndir.masterpassword.gui.*;
import com.lyndir.masterpassword.gui.MasterPassword;
import com.lyndir.masterpassword.gui.model.*; import com.lyndir.masterpassword.gui.model.*;
import com.lyndir.masterpassword.gui.util.*; import com.lyndir.masterpassword.gui.util.*;
import com.lyndir.masterpassword.gui.util.Platform; import com.lyndir.masterpassword.gui.util.Platform;
...@@ -577,21 +576,25 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, ...@@ -577,21 +576,25 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
components.add( Components.label( "Default Algorithm:" ), components.add( Components.label( "Default Algorithm:" ),
Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name, Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name,
user.getAlgorithm().version(), user.getAlgorithm().version(), version -> user.setAlgorithm( version.getAlgorithm() ) ) );
version -> user.setAlgorithm( version.getAlgorithm() ) ) );
MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null; components.add( Components.label( "Default Password Type:" ),
if (fileUser != null) { Components.comboBox( MPResultType.values(), MPResultType::getLongName,
components.add( Components.label( "Default Password Type:" ), user.getPreferences().getDefaultType(), user.getPreferences()::setDefaultType ),
Components.comboBox( MPResultType.values(), MPResultType::getLongName, Components.strut() );
fileUser.getPreferences().getDefaultType(),
fileUser.getPreferences()::setDefaultType ),
Components.strut() );
components.add( Components.checkBox( "Hide Passwords", components.add( Components.checkBox( "Hide Passwords",
fileUser.getPreferences().isHidePasswords(), user.getPreferences().isHidePasswords(), user.getPreferences()::setHidePasswords ) );
fileUser.getPreferences()::setHidePasswords ) );
} components.add( new JSeparator() );
components.add( Components.checkBox( "Check For Updates",
MPGuiConfig.get().checkForUpdates(), MPGuiConfig.get()::setCheckForUpdates ) );
components.add( Components.checkBox( strf( "<html>Stay Resident (reactivate with <strong><code>%s+%s</code></strong>)",
InputEvent.getModifiersExText( MPGuiConstants.ui_hotkey.getModifiers() ),
KeyEvent.getKeyText( MPGuiConstants.ui_hotkey.getKeyCode() ) ),
MPGuiConfig.get().stayResident(), MPGuiConfig.get()::setStayResident ) );
Components.showDialog( this, user.getFullName(), new JOptionPane( Components.panel( Components.showDialog( this, user.getFullName(), new JOptionPane( Components.panel(
BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) );
......
package com.lyndir.masterpassword.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.MutableClassToInstanceMap;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.model.impl.Changeable;
import com.lyndir.masterpassword.model.impl.MPJSONAnyObject;
import java.io.File;
import java.io.IOException;
/**
* @author lhunath, 2018-10-14
*/
@SuppressWarnings("CallToSystemGetenv")
public class MPConfig extends MPJSONAnyObject {
private static final Logger logger = Logger.get( MPConfig.class );
private static final ClassToInstanceMap<MPConfig> instances = MutableClassToInstanceMap.create();
private static final File configFile = new File( rcDir(), "config.json" );
private final Changeable changeable = new Changeable() {
@Override
protected void onChanged() {
try {
objectMapper.writerWithDefaultPrettyPrinter().writeValue( configFile, MPConfig.this );
instances.clear();
}
catch (final IOException e) {
logger.err( e, "While saving config to: %s", configFile );
}
}
};
protected static synchronized <C extends MPConfig> C get(final Class<C> type) {
C instance = instances.getInstance( type );
if (instance == null)
if (configFile.exists())
try {
instances.putInstance( type, instance = objectMapper.readValue( configFile, type ) );
}
catch (final IOException e) {
logger.wrn( e, "While reading config file: %s", configFile );
}
if (instance == null)
try {
instance = type.getConstructor().newInstance();
}
catch (final ReflectiveOperationException e) {
throw logger.bug( e );
}
return instance;
}
protected void setChanged() {
changeable.setChanged();
}
public static MPConfig get() {
return get( MPConfig.class );
}
public static File rcDir() {
String rcDir = System.getenv( MPModelConstants.env_rcDir );
if (rcDir != null)
return new File( rcDir );
String home = System.getProperty( "user.home" );
if (home == null)
home = System.getenv( "HOME" );
return new File( home, ".mpw.d" );
}
}
...@@ -40,14 +40,14 @@ public interface MPQuestion extends Comparable<MPQuestion> { ...@@ -40,14 +40,14 @@ public interface MPQuestion extends Comparable<MPQuestion> {
void setType(MPResultType type); void setType(MPResultType type);
@Nonnull @Nullable
default String getAnswer() default String getAnswer()
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
return getAnswer( null ); return getAnswer( null );
} }
@Nonnull @Nullable
String getAnswer(@Nullable String state) String getAnswer(@Nullable String state)
throws MPKeyUnavailableException, MPAlgorithmException; throws MPKeyUnavailableException, MPAlgorithmException;
......
...@@ -7,7 +7,7 @@ import java.util.concurrent.Executors; ...@@ -7,7 +7,7 @@ import java.util.concurrent.Executors;
/** /**
* @author lhunath, 2018-07-08 * @author lhunath, 2018-07-08
*/ */
public class Changeable { public abstract class Changeable {
private static final ExecutorService changeExecutor = Executors.newSingleThreadExecutor(); private static final ExecutorService changeExecutor = Executors.newSingleThreadExecutor();
...@@ -15,7 +15,9 @@ public class Changeable { ...@@ -15,7 +15,9 @@ public class Changeable {
private Grouping grouping = Grouping.APPLY; private Grouping grouping = Grouping.APPLY;
private boolean changed; private boolean changed;
void setChanged() { protected abstract void onChanged();
public void setChanged() {
synchronized (mutex) { synchronized (mutex) {
if (changed) if (changed)
return; return;
...@@ -37,9 +39,6 @@ public class Changeable { ...@@ -37,9 +39,6 @@ public class Changeable {
} ); } );
} }
protected void onChanged() {
}
public void beginChanges() { public void beginChanges() {
synchronized (mutex) { synchronized (mutex) {
grouping = Grouping.BATCH; grouping = Grouping.BATCH;
......
...@@ -72,7 +72,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion { ...@@ -72,7 +72,7 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {
setChanged(); setChanged();
} }
@Nonnull @Nullable
@Override @Override
public String getAnswer(@Nullable final String state) public String getAnswer(@Nullable final String state)
throws MPKeyUnavailableException, MPAlgorithmException { throws MPKeyUnavailableException, MPAlgorithmException {
...@@ -82,8 +82,6 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion { ...@@ -82,8 +82,6 @@ public abstract class MPBasicQuestion extends Changeable implements MPQuestion {