diff --git a/platform-darwin/External/Pearl b/platform-darwin/External/Pearl index b713577c..bc737d41 160000 --- a/platform-darwin/External/Pearl +++ b/platform-darwin/External/Pearl @@ -1 +1 @@ -Subproject commit b713577cd6cf07d516ec2be2220d9dc5f6ee1a4e +Subproject commit bc737d41fa15c9e4d05e7f73c25995da67611e52 diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java index 07274538..da528c89 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/model/MPNewSite.java @@ -1,7 +1,7 @@ package com.lyndir.masterpassword.gui.model; import com.lyndir.masterpassword.model.*; -import com.lyndir.masterpassword.model.impl.*; +import com.lyndir.masterpassword.model.impl.MPBasicSite; import javax.annotation.Nonnull; @@ -19,4 +19,14 @@ public class MPNewSite extends MPBasicSite, MPQuestion> { public MPQuestion addQuestion(final String keyword) { throw new UnsupportedOperationException( "Cannot add a question to a site that hasn't been created yet." ); } + + public > S addTo(final MPUser user) { + S site = user.addSite( getSiteName() ); + site.setAlgorithm( getAlgorithm() ); + site.setCounter( getCounter() ); + site.setLoginType( getLoginType() ); + site.setResultType( getResultType() ); + + return site; + } } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java index ba7fd994..1b1d80f8 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Components.java @@ -426,10 +426,18 @@ public abstract class Components { } public static JCheckBox checkBox(final String label) { + return checkBox( label, false, null ); + } + + public static JCheckBox checkBox(final String label, final boolean selected, @Nullable final Consumer selectionConsumer) { return new JCheckBox( label ) { { setBackground( null ); setAlignmentX( LEFT_ALIGNMENT ); + setSelected( selected ); + + if (selectionConsumer != null) + addItemListener( e -> selectionConsumer.accept( isSelected() ) ); } }; } diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java index a1b896ed..1992fa17 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/view/UserContentPanel.java @@ -28,6 +28,7 @@ import java.util.*; import java.util.Optional; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.swing.*; @@ -47,6 +48,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, private static final Logger logger = Logger.get( UserContentPanel.class ); private static final JButton iconButton = Components.button( Res.icons().user(), null, null ); private static final KeyStroke copyLoginKeyStroke = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK ); + private static final Pattern EACH_CHARACTER = Pattern.compile( "." ); private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(), "Add a new user to Master Password." ); @@ -570,17 +572,20 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, public void showUserPreferences() { ImmutableList.Builder components = ImmutableList.builder(); + components.add( Components.label( "Default Algorithm:" ), + Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name, + user.getAlgorithm().version(), + version -> user.setAlgorithm( version.getAlgorithm() ) ) ); + MPFileUser fileUser = (user instanceof MPFileUser)? (MPFileUser) user: null; - if (fileUser != null) + if (fileUser != null) { components.add( Components.label( "Default Password Type:" ), Components.comboBox( MPResultType.values(), MPResultType::getLongName, fileUser.getDefaultType(), fileUser::setDefaultType ), Components.strut() ); - components.add( Components.label( "Default Algorithm:" ), - Components.comboBox( MPAlgorithm.Version.values(), MPAlgorithm.Version::name, - user.getAlgorithm().version(), - version -> user.setAlgorithm( version.getAlgorithm() ) ) ); + components.add( Components.checkBox( "Hide Passwords", fileUser.isHidePasswords(), fileUser::setHidePasswords ) ); + } Components.showDialog( this, user.getFullName(), new JOptionPane( Components.panel( BoxLayout.PAGE_AXIS, components.build().toArray( new Component[0] ) ) ) ); @@ -846,7 +851,7 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, "New Site", JOptionPane.YES_NO_OPTION )) return; - site = user.addSite( site.getSiteName() ); + site = ((MPNewSite) site).addTo( user ); } boolean loginResult = (copyLoginKeyStroke.getModifiers() & event.getModifiers()) != 0; @@ -878,7 +883,12 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, else if (showLogin && (site != null)) resultLabel.setText( (result != null)? strf( "Your login for %s:", site.getSiteName() ): " " ); - resultField.setText( (result != null)? result: " " ); + if ((result == null) || result.isEmpty()) + resultField.setText( " " ); + else if (!showLogin && (user instanceof MPFileUser) && ((MPFileUser) user).isHidePasswords()) + resultField.setText( EACH_CHARACTER.matcher( result ).replaceAll( "•" ) ); + else + resultField.setText( result ); settingsButton.setEnabled( result != null ); questionsButton.setEnabled( result != null ); editButton.setEnabled( result != null ); @@ -1016,8 +1026,14 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener, new LinkedList<>( user.findSites( query ) ); if (!Strings.isNullOrEmpty( queryText )) - if (siteItems.stream().noneMatch( MPQuery.Result::isExact )) - siteItems.add( MPQuery.Result.allOf( new MPNewSite( user, query.getQuery() ), query.getQuery() ) ); + if (siteItems.stream().noneMatch( MPQuery.Result::isExact )) { + MPQuery.Result> selectedItem = sitesModel.getSelectedItem(); + if ((selectedItem != null) && user.equals( selectedItem.getOption().getUser() ) && + queryText.equals( selectedItem.getOption().getSiteName() )) + siteItems.add( selectedItem ); + else + siteItems.add( MPQuery.Result.allOf( new MPNewSite( user, query.getQuery() ), query.getQuery() ) ); + } Res.ui( () -> sitesModel.set( siteItems ) ); } ); diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java index 2d825b49..8434a575 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFileUser.java @@ -47,6 +47,7 @@ public class MPFileUser extends MPBasicUser { private MPResultType defaultType; private ReadableInstant lastUsed; + private boolean hidePasswords; private boolean complete; @Nullable @@ -54,7 +55,7 @@ public class MPFileUser extends MPBasicUser { throws IOException, MPMarshalException { for (final MPMarshalFormat format : MPMarshalFormat.values()) if (file.getName().endsWith( format.fileSuffix() )) - return format.unmarshaller().readUser( file ); + return format.unmarshaller().readUser( file ); return null; } @@ -64,18 +65,19 @@ public class MPFileUser extends MPBasicUser { } 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(), false, MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, path ); } - public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, - final int avatar, @Nullable final MPResultType defaultType, final ReadableInstant lastUsed, + public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final int avatar, + @Nullable final MPResultType defaultType, final ReadableInstant lastUsed, final boolean hidePasswords, final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File path) { super( avatar, fullName, algorithm ); this.keyID = (keyID != null)? keyID.clone(): null; this.defaultType = (defaultType != null)? defaultType: algorithm.mpw_default_result_type(); this.lastUsed = lastUsed; + this.hidePasswords = hidePasswords; this.path = path; this.format = format; this.contentMode = contentMode; @@ -157,6 +159,18 @@ public class MPFileUser extends MPBasicUser { setChanged(); } + public boolean isHidePasswords() { + return hidePasswords; + } + + public void setHidePasswords(final boolean hidePasswords) { + if (Objects.equals( this.hidePasswords, hidePasswords )) + return; + + this.hidePasswords = hidePasswords; + setChanged(); + } + protected boolean isComplete() { return complete; } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java index 3c10fb34..ba6ae53f 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPFlatUnmarshaller.java @@ -67,7 +67,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller { else if ((fullName != null) && (keyID != null)) // Ends the header. return new MPFileUser( fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(), - avatar, defaultType, new Instant( 0 ), + avatar, defaultType, new Instant( 0 ), false, clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.Flat, file.getParentFile() ); } diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java index fd092c74..31509438 100644 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPJSONFile.java @@ -28,8 +28,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.primitives.UnsignedInteger; import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.masterpassword.*; -import com.lyndir.masterpassword.model.MPModelConstants; import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException; +import com.lyndir.masterpassword.model.MPModelConstants; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.File; import java.util.LinkedHashMap; @@ -77,6 +77,7 @@ public class MPJSONFile extends MPJSONAnyObject { user.avatar = modelUser.getAvatar(); user.full_name = modelUser.getFullName(); user.last_used = MPModelConstants.dateTimeFormatter.print( modelUser.getLastUsed() ); + user.hide_passwords = modelUser.isHidePasswords(); user.key_id = modelUser.exportKeyID(); user.algorithm = modelUser.getAlgorithm().version(); user.default_type = modelUser.getDefaultType(); @@ -142,7 +143,7 @@ public class MPJSONFile extends MPJSONAnyObject { user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar, (user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(), (user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(), - export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE, + user.hide_passwords, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE, MPMarshalFormat.JSON, file.getParentFile() ); } @@ -202,9 +203,10 @@ public class MPJSONFile extends MPJSONAnyObject { public static class User extends MPJSONAnyObject { - int avatar; - String full_name; - String last_used; + int avatar; + String full_name; + String last_used; + boolean hide_passwords; @Nullable String key_id; @Nullable diff --git a/public/site b/public/site index 914a60cd..d8d510b6 160000 --- a/public/site +++ b/public/site @@ -1 +1 @@ -Subproject commit 914a60cd25707f4ac456ad225580a86a5a95e637 +Subproject commit d8d510b6be2e2136a040624b8d0ed7590b6e7530