From 37a7cfa5303fb848c961ad5dba9e8ccd4c7c8fb8 Mon Sep 17 00:00:00 2001 From: Maarten Billemont Date: Sat, 28 Jul 2018 21:53:08 -0400 Subject: [PATCH] Support resetting user's master password. --- .../lyndir/masterpassword/MPMasterKey.java | 6 ++- .../masterpassword/gui/util/Components.java | 15 +++++- .../lyndir/masterpassword/gui/util/Res.java | 4 ++ .../gui/view/UserContentPanel.java | 47 ++++++++++++++++-- .../src/main/resources/media/icon_reset.png | Bin 0 -> 2020 bytes .../main/resources/media/icon_reset@2x.png | Bin 0 -> 3137 bytes .../lyndir/masterpassword/model/MPUser.java | 10 ++++ .../model/impl/MPBasicUser.java | 20 +++++--- .../masterpassword/model/impl/MPFileUser.java | 7 +++ 9 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 platform-independent/java/gui/src/main/resources/media/icon_reset.png create mode 100644 platform-independent/java/gui/src/main/resources/media/icon_reset@2x.png diff --git a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java index 0ef0e20b..15c6d458 100644 --- a/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java +++ b/platform-independent/java/algorithm/src/main/java/com/lyndir/masterpassword/MPMasterKey.java @@ -82,11 +82,15 @@ public class MPMasterKey { Arrays.fill( masterPassword, (char) 0 ); } + public boolean isValid() { + return !invalidated; + } + private byte[] masterKey(final MPAlgorithm algorithm) throws MPKeyUnavailableException, MPAlgorithmException { Preconditions.checkArgument( masterPassword.length > 0 ); - if (invalidated) + if (!isValid()) throw new MPKeyUnavailableException( "Master key was invalidated." ); byte[] masterKey = keyByVersion.get( algorithm.version() ); 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 8bc68a9f..a3d7ba04 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 @@ -21,6 +21,7 @@ package com.lyndir.masterpassword.gui.util; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.util.Arrays; import java.util.Collection; import java.util.function.Consumer; import java.util.function.Function; @@ -91,11 +92,21 @@ public abstract class Components { return new GradientPanel( layout, color ); } - public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) { + public static int showDialog(@Nullable final Component owner, @Nullable final String title, final JOptionPane pane) { JDialog dialog = pane.createDialog( owner, title ); dialog.setModalityType( Dialog.ModalityType.DOCUMENT_MODAL ); + showDialog( dialog ); - return showDialog( dialog ); + Object selectedValue = pane.getValue(); + if(selectedValue == null) + return JOptionPane.CLOSED_OPTION; + + Object[] options = pane.getOptions(); + if(options == null) + return (selectedValue instanceof Integer)? (Integer) selectedValue: JOptionPane.CLOSED_OPTION; + + int option = Arrays.binarySearch( options, selectedValue ); + return (option < 0)? JOptionPane.CLOSED_OPTION: option; } public static JDialog showDialog(@Nullable final Component owner, @Nullable final String title, final Container content) { diff --git a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java index cffcc92f..e4bba494 100644 --- a/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java +++ b/platform-independent/java/gui/src/main/java/com/lyndir/masterpassword/gui/util/Res.java @@ -132,6 +132,10 @@ public abstract class Res { return icon( "media/icon_lock.png" ); } + public Icon reset() { + return icon( "media/icon_reset.png" ); + } + public Icon settings() { return icon( "media/icon_settings.png" ); } 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 795bb580..65c14784 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 @@ -37,8 +37,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU private static final Logger logger = Logger.get( UserContentPanel.class ); private static final JButton iconButton = Components.button( Res.icons().user(), null, null ); - private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(), - "Add a new user to Master Password." ); + private final JButton addButton = Components.button( Res.icons().add(), event -> addUser(), + "Add a new user to Master Password." ); private final JPanel userToolbar = Components.panel( BoxLayout.PAGE_AXIS ); private final JPanel siteToolbar = Components.panel( BoxLayout.PAGE_AXIS ); @@ -142,6 +142,8 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU private final JButton deleteButton = Components.button( Res.icons().delete(), event -> deleteUser(), "Delete this user from Master Password." ); + private final JButton resetButton = Components.button( Res.icons().reset(), event -> resetUser(), + "Change the master password for this user." ); private final JPasswordField masterPasswordField = Components.passwordField(); private final JLabel errorLabel = Components.label(); @@ -156,6 +158,7 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU userToolbar.add( addButton ); userToolbar.add( deleteButton ); + userToolbar.add( resetButton ); add( Components.heading( user.getFullName(), SwingConstants.CENTER ) ); add( Components.strut() ); @@ -180,12 +183,48 @@ public class UserContentPanel extends JPanel implements FilesPanel.Listener, MPU return; if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog( - this, strf( "Delete the user %s?

%s", - fileUser.getFullName(), fileUser.getFile().getName() ), + SwingUtilities.windowForComponent( this ), strf( "Delete the user %s?

%s", + fileUser.getFullName(), fileUser.getFile().getName() ), "Delete User", JOptionPane.YES_NO_OPTION )) MPFileUserManager.get().delete( fileUser ); } + private void resetUser() { + JPasswordField passwordField = Components.passwordField(); + if (JOptionPane.OK_OPTION == Components.showDialog( this, "Reset User", new JOptionPane( Components.panel( + BoxLayout.PAGE_AXIS, + Components.label( strf( "Enter the new master password for %s:", + user.getFullName() ) ), + Components.strut(), + passwordField, + Components.strut(), + Components.label( strf( "Note:
Changing the master password " + + "will change all of the user's passwords.
" + + "Changing back to the original master password will also restore
" + + "the user's original passwords.
", + user.getFullName() ) ) ), JOptionPane.QUESTION_MESSAGE, JOptionPane.OK_CANCEL_OPTION ) { + @Override + public void selectInitialValue() { + passwordField.requestFocusInWindow(); + } + } )) { + char[] masterPassword = passwordField.getPassword(); + if ((masterPassword != null) && (masterPassword.length > 0)) + try { + user.reset(); + user.authenticate( masterPassword ); + } + catch (final MPIncorrectMasterPasswordException e) { + errorLabel.setText( e.getLocalizedMessage() ); + throw logger.bug( e ); + } + catch (final MPAlgorithmException e) { + logger.err( e, "While resetting master password." ); + errorLabel.setText( e.getLocalizedMessage() ); + } + } + } + @Override public void actionPerformed(final ActionEvent event) { updateIdenticon(); diff --git a/platform-independent/java/gui/src/main/resources/media/icon_reset.png b/platform-independent/java/gui/src/main/resources/media/icon_reset.png new file mode 100644 index 0000000000000000000000000000000000000000..754d5863c6dcef76e7cf099e93cfda99aacd0363 GIT binary patch literal 2020 zcmbVNdsGu=77t=mb+r(nE^v5^0i?K)WJ1WRMogFl;u_!pYAQ+z$&k#FWMVQ%z_lPP zAd3epKAT2xd@iciW?7AykdS_Bc;6?rUjfC`bFD7d@lc>2fAIWzM;?(g@z z_kQ=DFE=tgbkz#i6$Aodl{gHF!vBEvT}H&ehP1Rv0>L&BiIHMb$!4w`)lg&#Gy$d< zH9CBsKv*Aa)XC&YFa{*RN<_;ekJbG^1`q|0ye&vVljuTV6%v-FhojTNW8`T`a*l!= zya8Bmkkgnn zfC*B-Kn9D&@(1WNkVd7^sUST7WN`!OTpA5nc*wY#UXjR+f4zZVVI6frKY5$ zP*MUZs9s40IUJ6agH8{?EdmUwT1;jP&>FlJ86emo*CRR%LA8LDQI>!vV>~kM>6;KV zI*H^hvDUDVC_H6UqfAEyDKx4^V~y*zwE>HQ|Kr9xtqn1$I+z*-8_;CE9LCMP7QuM# zep|?DhP-ah=_we+rIFyfhSiR5355lNm+EqppJdD zv$OM6MMZ_@xvj&(wzjtMy(n58;&kW8vmED7j$Ioa9W5NnYx9XuOw1k-w7XWHIrD3F z$(ZkyJ!)S~syi}U8?d_B&w)-4rmW86oUN+rqJ(W+SDbk5D$gT*%a)FhrQ|0rOOke! zXY6sj=q@m`(_dY8EnG|L@9(cXYAKJ%@UE0n^y^w6aJ0PTvQ6|^QVtR)KH|_h%+ju` zx|pkoI#*Ryqq>VM58LF>E+~N2rY4h#A|Y*>TXAzoT%0-Bd4>2-k9ynMLP~eX+58JC z7_fDJX20*N2im%q?HR5^829}jr!INeP~+Wm<*sf%A2?Jy4)6D!Sk|B~+OT2o;Nc=? zWriK`mLj$~bmJj%nZMo4xsq*j6D@@8yY}w2>aWtK45Q_*|#PPMtSZu$JkZEY=D9x>|RetBfFF5YIz*6WW(o1Ke(H~v%5z)=?3 z^Ihnea{rY-W7z%U;gzr&SmP?>@^ZaT%f~+)|F8^Xj=WGJmI;AfckMqUkm@}jAD@BG zQ%3VsIi9u&M`eV)1}`hXZOUMEVg+%xo!7z_ETqf zM_#*|t!&L-9K-V=@`zib`^loB2Lt+RXCG;&;CX5&vf$3 zs*dCF{1abFZAn>KS!2bs&tJq&`xYZz_ZfSfrtkVjD}#9{?J9;xB=OUlwoGqUrF?B1 zd3WKlV_s<&7sVNx=a^6MeP*q!y%I(KJ(w@uRnMI literal 0 HcmV?d00001 diff --git a/platform-independent/java/gui/src/main/resources/media/icon_reset@2x.png b/platform-independent/java/gui/src/main/resources/media/icon_reset@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..be4eb451833c1452dc7068e24a72d1dbb051b964 GIT binary patch literal 3137 zcmbVMc{r478-E95jjUPAG^q%)7-P(2nHe*(FGZi!`nlzG9R7$7FQNCBF&i8%Sb*}Tr_gvTW-p}&8fA{j-_j}C8Ym2I~fieI9 zs;(}St)L_>Pepm~`wJuX2>{4OGW-Jg0iGU&a5fV`i(rS*5dtO$yaxa~M*)WxzL(C2 zh0&uJEPMD&V+$O{h_Hw6z>Gr?*B(w5pqOB~^I0D9sfI`8(4>)Lz8xcv^N^$;f3*6bmqxpOe0f~%{k4MB~5NvJ~ z5{<{>mpQDgEJ1`NFM-9U2`pK>bw3y=bY3`@!QnI5EZ8z5EsVX7Zx07O{WAn6$J6sK zVixavqQI0P1vCy4jX)up%;mVgp?UnR^#8c=w`iVU0*8*=O6Rfnal`2#eBBRaFn9lK zXc-84L-6J@z(=9QP}t%7m~cP{obh1KUcUPYvL2V@LAiX|9IyXkfy z$cRDl_5-C1IF|%0(`ZN5fbLnZmHOFe0~zq&>A zotf`-r|D_hn^ZvIX?heZT8=vD1t;Z8U+M~tb*J=Aw|i?;1^X>#X=)w1-43%xoqxgS zHA+V9k`HhP_ch%ajhkF^UBLksHCHHYDB<0kF{;TxU-+Y>k0E0XDF8f$8;Dw^O1*N? zGTRefaC3nPaw#^c9-%TdZ5ziT(>w1@zrj?4H4u z;&l89E22_`O}@t{h80gltYIolsF|~kS3^>V?q-i`brM5Dq?WbrrKH3Awj4zv#`Wr5 zBo&u0Uk*a7@hWfVNb}D$#6s4zf!MkGMUwqS{#1=6(b<8{lw>>hIIVn@m^GMT~v+q6xtV~52zeL3eFFHRl}w3{$)3q_e!B3iM}BA>#K zt7t02wTX1h>Pybg>Q*%b$jp)s95~S2KXEBQIDowgPeC9~xhQVh+mCBf=2D77m5%Y=n?$d^tWsFD3==UayYGy z82XHzR~%L^1KhY>yLauDdgF^c?$o@0@B39_3l^djNZxbEXng32%MKuCVq;yMY6fFt zR#*wY{$+>HALY?8El&`Zt`D$S|Ez5TF-jR`j@{e)XF|XhURsj7vC$j=?D#cnvHw#7>weSZ zQsKYeHl6fPT-&G~@v1iKXU6MC@oH->^G-BS;p?jVP;aFLxzeSpz>d{@(FKL&;cyMn zgQ?Ch5?-*-xbD%)_q9~`?Q%?GpuE$zx7u`+H zeqK!1istgXRC-58gRnLZYhSOsXA)d7@>#yXqK6KdK%Ab=mTh}vv~HAk`^R|Mt zKjkD?8>}vEy?M$dW{w_Dc(w7Ek68|&7)(v}DZ}h!y4v{%C7Tp~ef6+vrwS2js<$GJ z44@sJlDgt(wZmcc?+u4yHwR#cWLhV+CeAFR&EaxB)oeb35;IHlZke73c3krNMOD7M zJE}H-3J-2^%vCD$sbFO&mmBF{D@po!=GDUB+((7&x!JF84nORbH**L&rx~gtC{66j z5Yt3d6SC~=eA4*Lb#?pnlZ_a~{i~CH&qSUa(*000EY0lG9u4%3)ee6o!#eJmYz7Tb zbZZr}rg>#w?&G1|#V2Tv*t{HD08`Odo%uQq4w27kvoWYgu&fMR*`gcz$z z=4`vCE>2* zRpA1S%ko3-htKK2f)x`gM*xE4Z#W!?*>U=gRM@6h zHL_E3xOuuQ@rq#2lsf_l*$w2Tc$&1ApY$(U3|flfaE48`lFZ!0e!AY(d8o_q%#d92 zMeA#{gmNHiwrq1viG{gz_+H!TgNBR&%xJ9dY!S?&gj(uRVU7s?%8*<|p1?pel?on~ zAB?XKh)o;VZ?8~-bf@hcQRZ#1fb4Cycc&Pc=I_YGnwJDuuw$Nw$$dZ7El3t_-3q~0RUMgWUTPZ|xb)$p z;Sp%8+aFo!)lJg=vr> extends Comparable> { void authenticate(MPMasterKey masterKey) throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException; + /** + * Clear all authentication tokens and secrets from memory, effectively logging the user out. + */ void invalidate(); + /** + * Wipe the key ID, allowing the user to {@link #authenticate(char[])} with any master password. + * + * Note: Authenticating with a different master password will cause all of the user's results to change. + */ + void reset(); + boolean isMasterKeyAvailable(); @Nonnull diff --git a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java index 2c73f915..eef907c2 100755 --- a/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java +++ b/platform-independent/java/model/src/main/java/com/lyndir/masterpassword/model/impl/MPBasicUser.java @@ -97,12 +97,14 @@ public abstract class MPBasicUser> extends Changeabl @Override public byte[] getKeyID() { try { - return getMasterKey().getKeyID( getAlgorithm() ); + if (isMasterKeyAvailable()) + return getMasterKey().getKeyID( getAlgorithm() ); } catch (final MPException e) { logger.wrn( e, "While deriving key ID for user: %s", this ); - return null; } + + return null; } @Nullable @@ -143,23 +145,29 @@ public abstract class MPBasicUser> extends Changeabl public void invalidate() { if (masterKey == null) return; - - this.masterKey = null; + + masterKey.invalidate(); + masterKey = null; for (final Listener listener : listeners) listener.onUserInvalidated( this ); } + @Override + public void reset() { + invalidate(); + } + @Override public boolean isMasterKeyAvailable() { - return masterKey != null; + return (masterKey != null) && masterKey.isValid(); } @Nonnull @Override public MPMasterKey getMasterKey() throws MPKeyUnavailableException { - if (masterKey == null) + if ((masterKey == null) || !masterKey.isValid()) throw new MPKeyUnavailableException( "Master key was not yet set for: " + this ); return masterKey; 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 a1c4b181..acbdf2bc 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 @@ -169,6 +169,13 @@ public class MPFileUser extends MPBasicUser { } } + @Override + public void reset() { + keyID = null; + + super.reset(); + } + @Override public MPFileSite addSite(final String siteName) { return addSite( new MPFileSite( this, siteName ) );