2
0

Deep Java refactoring to match the C API logic and clean up some OO oddities.

This commit is contained in:
Maarten Billemont 2017-09-22 19:03:50 -04:00
parent dc7089c38c
commit 5d1be43b65
42 changed files with 823 additions and 1157 deletions

View File

@ -24,11 +24,11 @@ import javax.annotation.Nullable;
/** /**
* @see MasterKey.Version * @see MPMasterKey.Version
*/ */
public interface MasterKeyAlgorithm extends Serializable { public interface MPAlgorithm extends Serializable {
MasterKey.Version getAlgorithmVersion(); MPMasterKey.Version getAlgorithmVersion();
byte[] deriveKey(String fullName, char[] masterPassword); byte[] deriveKey(String fullName, char[] masterPassword);

View File

@ -38,11 +38,11 @@ import javax.crypto.IllegalBlockSizeException;
/** /**
* @see MasterKey.Version#V0 * @see MPMasterKey.Version#V0
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV0 implements MasterKeyAlgorithm { public class MPAlgorithmV0 implements MPAlgorithm {
/** /**
* mpw: validity for the time-based rolling counter. * mpw: validity for the time-based rolling counter.
@ -84,9 +84,9 @@ public class MasterKeyV0 implements MasterKeyAlgorithm {
protected final Logger logger = Logger.get( getClass() ); protected final Logger logger = Logger.get( getClass() );
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public MPMasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.V0; return MPMasterKey.Version.V0;
} }
@Override @Override

View File

@ -23,16 +23,16 @@ import javax.annotation.Nullable;
/** /**
* @see MasterKey.Version#V1 * @see MPMasterKey.Version#V1
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV1 extends MasterKeyV0 { public class MPAlgorithmV1 extends MPAlgorithmV0 {
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public MPMasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.V1; return MPMasterKey.Version.V1;
} }
@Override @Override

View File

@ -28,16 +28,16 @@ import javax.annotation.Nullable;
/** /**
* @see MasterKey.Version#V2 * @see MPMasterKey.Version#V2
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV2 extends MasterKeyV1 { public class MPAlgorithmV2 extends MPAlgorithmV1 {
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public MPMasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.V2; return MPMasterKey.Version.V2;
} }
@Override @Override
@ -56,25 +56,25 @@ public class MasterKeyV2 extends MasterKeyV1 {
// OTP counter value. // OTP counter value.
if (siteCounter.longValue() == 0) if (siteCounter.longValue() == 0)
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MasterKeyV0.mpw_otp_window * 1000)) * MasterKeyV0.mpw_otp_window ); siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (MPAlgorithmV0.mpw_otp_window * 1000)) * MPAlgorithmV0.mpw_otp_window );
// Calculate the site seed. // Calculate the site seed.
byte[] siteNameBytes = siteName.getBytes( MasterKeyV0.mpw_charset ); byte[] siteNameBytes = siteName.getBytes( MPAlgorithmV0.mpw_charset );
byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length ); byte[] siteNameLengthBytes = bytesForInt( siteNameBytes.length );
byte[] siteCounterBytes = bytesForInt( siteCounter ); byte[] siteCounterBytes = bytesForInt( siteCounter );
byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MasterKeyV0.mpw_charset ); byte[] keyContextBytes = ((keyContext == null) || keyContext.isEmpty())? null: keyContext.getBytes( MPAlgorithmV0.mpw_charset );
byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length ); byte[] keyContextLengthBytes = (keyContextBytes == null)? null: bytesForInt( keyContextBytes.length );
logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s", logger.trc( "siteSalt: keyScope=%s | #siteName=%s | siteName=%s | siteCounter=%s | #keyContext=%s | keyContext=%s",
keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ), keyScope, CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
(keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext ); (keyContextLengthBytes == null)? null: CodeUtils.encodeHex( keyContextLengthBytes ), keyContext );
byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MasterKeyV0.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes ); byte[] sitePasswordInfo = Bytes.concat( keyScope.getBytes( MPAlgorithmV0.mpw_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
if (keyContextBytes != null) if (keyContextBytes != null)
sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes ); sitePasswordInfo = Bytes.concat( sitePasswordInfo, keyContextLengthBytes, keyContextBytes );
logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) ); logger.trc( " => siteSalt.id: %s", CodeUtils.encodeHex( idForBytes( sitePasswordInfo ) ) );
logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) ); logger.trc( "siteKey: hmac-sha256( masterKey.id=%s, siteSalt )", (Object) idForBytes( masterKey ) );
byte[] sitePasswordSeedBytes = MasterKeyV0.mpw_digest.of( masterKey, sitePasswordInfo ); byte[] sitePasswordSeedBytes = MPAlgorithmV0.mpw_digest.of( masterKey, sitePasswordInfo );
logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) ); logger.trc( " => siteKey.id: %s", (Object) idForBytes( sitePasswordSeedBytes ) );
return sitePasswordSeedBytes; return sitePasswordSeedBytes;

View File

@ -29,25 +29,25 @@ import java.util.Arrays;
/** /**
* @see MasterKey.Version#V3 * @see MPMasterKey.Version#V3
* *
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKeyV3 extends MasterKeyV2 { public class MPAlgorithmV3 extends MPAlgorithmV2 {
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public MPMasterKey.Version getAlgorithmVersion() {
return MasterKey.Version.V3; return MPMasterKey.Version.V3;
} }
@Override @Override
public byte[] deriveKey(final String fullName, final char[] masterPassword) { public byte[] deriveKey(final String fullName, final char[] masterPassword) {
Preconditions.checkArgument( masterPassword.length > 0 ); Preconditions.checkArgument( masterPassword.length > 0 );
byte[] fullNameBytes = fullName.getBytes( MasterKeyV0.mpw_charset ); byte[] fullNameBytes = fullName.getBytes( MPAlgorithmV0.mpw_charset );
byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length ); byte[] fullNameLengthBytes = MPUtils.bytesForInt( fullNameBytes.length );
ByteBuffer mpBytesBuf = MasterKeyV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) ); ByteBuffer mpBytesBuf = MPAlgorithmV0.mpw_charset.encode( CharBuffer.wrap( masterPassword ) );
logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() ); logger.trc( "-- mpw_masterKey (algorithm: %u)", getAlgorithmVersion().toInt() );
logger.trc( "fullName: %s", fullName ); logger.trc( "fullName: %s", fullName );
@ -59,12 +59,12 @@ public class MasterKeyV3 extends MasterKeyV2 {
// Calculate the master key salt. // Calculate the master key salt.
logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s", logger.trc( "masterKeySalt: keyScope=%s | #fullName=%s | fullName=%s",
keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName ); keyScope, CodeUtils.encodeHex( fullNameLengthBytes ), fullName );
byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MasterKeyV0.mpw_charset ), fullNameLengthBytes, fullNameBytes ); byte[] masterKeySalt = Bytes.concat( keyScope.getBytes( MPAlgorithmV0.mpw_charset ), fullNameLengthBytes, fullNameBytes );
logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) ); logger.trc( " => masterKeySalt.id: %s", CodeUtils.encodeHex( idForBytes( masterKeySalt ) ) );
// Calculate the master key. // Calculate the master key.
logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )", logger.trc( "masterKey: scrypt( masterPassword, masterKeySalt, N=%lu, r=%u, p=%u )",
MasterKeyV0.scrypt_N, MasterKeyV0.scrypt_r, MasterKeyV0.scrypt_p ); MPAlgorithmV0.scrypt_N, MPAlgorithmV0.scrypt_r, MPAlgorithmV0.scrypt_p );
byte[] mpBytes = new byte[mpBytesBuf.remaining()]; byte[] mpBytes = new byte[mpBytesBuf.remaining()];
mpBytesBuf.get( mpBytes, 0, mpBytes.length ); mpBytesBuf.get( mpBytes, 0, mpBytes.length );
Arrays.fill( mpBytesBuf.array(), (byte) 0 ); Arrays.fill( mpBytesBuf.array(), (byte) 0 );

View File

@ -0,0 +1,25 @@
//==============================================================================
// 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;
/**
* @author lhunath, 2017-09-21
*/
public class MPInvalidatedException extends Exception {
}

View File

@ -20,9 +20,10 @@ package com.lyndir.masterpassword;
import static com.lyndir.masterpassword.MPUtils.idForBytes; import static com.lyndir.masterpassword.MPUtils.idForBytes;
import com.google.common.base.Preconditions;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Arrays;
import java.util.EnumMap;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -30,26 +31,28 @@ import javax.annotation.Nullable;
/** /**
* @author lhunath, 2014-08-30 * @author lhunath, 2014-08-30
*/ */
public class MasterKey { public class MPMasterKey {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKey.class ); private static final Logger logger = Logger.get( MPMasterKey.class );
private final EnumMap<Version, byte[]> keyByVersion = new EnumMap<>( Version.class );
private final String fullName; private final String fullName;
private final char[] masterPassword; private final char[] masterPassword;
private boolean invalidated;
/**
* @param masterPassword The characters of the user's master password. Note: this array is held by reference and its contents
* invalidated on {@link #invalidate()}.
*/
@SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter") @SuppressWarnings("AssignmentToCollectionOrArrayFieldFromParameter")
public MasterKey(final String fullName, final char[] masterPassword) { public MPMasterKey(final String fullName, final char[] masterPassword) {
this.fullName = fullName; this.fullName = fullName;
this.masterPassword = masterPassword; this.masterPassword = masterPassword;
} }
private byte[] getKey(final Version algorithmVersion) {
// TODO: Cache keys.
return algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword );
}
/** /**
* Generate a site result token. * Generate a site result token.
* *
@ -60,10 +63,13 @@ public class MasterKey {
* @param resultType The type of result to generate. * @param resultType The type of result to generate.
* @param resultParam A parameter for the resultType. For stateful result types, the output of * @param resultParam A parameter for the resultType. For stateful result types, the output of
* {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}. * {@link #siteState(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/ */
public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, public String siteResult(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam, @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion) { final Version algorithmVersion)
throws MPInvalidatedException {
return algorithmVersion.getAlgorithm().siteResult( return algorithmVersion.getAlgorithm().siteResult(
getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam ); getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
} }
@ -78,10 +84,13 @@ public class MasterKey {
* @param resultType The type of result token to encrypt. * @param resultType The type of result token to encrypt.
* @param resultParam The result token desired from * @param resultParam The result token desired from
* {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}. * {@link #siteResult(String, UnsignedInteger, MPKeyPurpose, String, MPResultType, String, Version)}.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/ */
public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose, public String siteState(final String siteName, final UnsignedInteger siteCounter, final MPKeyPurpose keyPurpose,
@Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam, @Nullable final String keyContext, final MPResultType resultType, @Nullable final String resultParam,
final Version algorithmVersion) { final Version algorithmVersion)
throws MPInvalidatedException {
return algorithmVersion.getAlgorithm().siteState( return algorithmVersion.getAlgorithm().siteState(
getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam ); getKey( algorithmVersion ), siteName, siteCounter, keyPurpose, keyContext, resultType, resultParam );
} }
@ -92,45 +101,81 @@ public class MasterKey {
return fullName; return fullName;
} }
public byte[] getKeyID(final Version algorithmVersion) { /**
* Calculate an identifier for the master key.
*
* @throws MPInvalidatedException {@link #invalidate()} has been called on this object.
*/
public byte[] getKeyID(final Version algorithmVersion)
throws MPInvalidatedException {
return idForBytes( getKey( algorithmVersion ) ); return idForBytes( getKey( algorithmVersion ) );
} }
/**
* Wipe this key's secrets from memory, making the object permanently unusable.
*/
public void invalidate() {
invalidated = true;
for (final byte[] key : keyByVersion.values())
Arrays.fill( key, (byte) 0 );
Arrays.fill( masterPassword, (char) 0 );
}
private byte[] getKey(final Version algorithmVersion)
throws MPInvalidatedException {
if (invalidated)
throw new MPInvalidatedException();
byte[] key = keyByVersion.get( algorithmVersion );
if (key == null)
keyByVersion.put( algorithmVersion, key = algorithmVersion.getAlgorithm().deriveKey( fullName, masterPassword ) );
return key;
}
/**
* The algorithm iterations.
*/
public enum Version { public enum Version {
/** /**
* bugs: * bugs:
* - does math with chars whose signedness was platform-dependent. * - does math with chars whose signedness was platform-dependent.
* - miscounted the byte-length for multi-byte site names. * - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte full names. * - miscounted the byte-length for multi-byte user names.
*/ */
V0( new MasterKeyV0() ), V0( new MPAlgorithmV0() ),
/** /**
* bugs: * bugs:
* - miscounted the byte-length for multi-byte site names. * - miscounted the byte-length for multi-byte site names.
* - miscounted the byte-length for multi-byte full names. * - miscounted the byte-length for multi-byte user names.
*/ */
V1( new MasterKeyV1() ), V1( new MPAlgorithmV1() ),
/** /**
* bugs: * bugs:
* - miscounted the byte-length for multi-byte full names. * - miscounted the byte-length for multi-byte user names.
*/ */
V2( new MasterKeyV2() ), V2( new MPAlgorithmV2() ),
/** /**
* bugs: * bugs:
* - no known issues. * - no known issues.
*/ */
V3( new MasterKeyV3() ); V3( new MPAlgorithmV3() );
public static final Version CURRENT = V3; public static final Version CURRENT = V3;
private final MasterKeyAlgorithm algorithm; private final MPAlgorithm algorithm;
Version(final MasterKeyAlgorithm algorithm) { Version(final MPAlgorithm algorithm) {
this.algorithm = algorithm; this.algorithm = algorithm;
} }
public MasterKeyAlgorithm getAlgorithm() { public MPAlgorithm getAlgorithm() {
return algorithm; return algorithm;
} }

View File

@ -28,14 +28,14 @@ import java.nio.ByteBuffer;
public final class MPUtils { public final class MPUtils {
public static byte[] bytesForInt(final int number) { public static byte[] bytesForInt(final int number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number ).array(); return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithmV0.mpw_byteOrder ).putInt( number ).array();
} }
public static byte[] bytesForInt(final UnsignedInteger number) { public static byte[] bytesForInt(final UnsignedInteger number) {
return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MasterKeyV0.mpw_byteOrder ).putInt( number.intValue() ).array(); return ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( MPAlgorithmV0.mpw_byteOrder ).putInt( number.intValue() ).array();
} }
public static byte[] idForBytes(final byte[] bytes) { public static byte[] idForBytes(final byte[] bytes) {
return MasterKeyV0.mpw_hash.of( bytes ); return MPAlgorithmV0.mpw_hash.of( bytes );
} }
} }

View File

@ -0,0 +1,205 @@
//==============================================================================
// 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.model;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*;
import javax.annotation.Nullable;
import org.joda.time.Instant;
/**
* @author lhunath, 14-12-05
*/
public class MPFileSite extends MPSite {
private final MPFileUser user;
private String siteName;
@Nullable
private String siteContent;
private UnsignedInteger siteCounter;
private MPResultType resultType;
private MPMasterKey.Version algorithmVersion;
@Nullable
private String loginContent;
@Nullable
private MPResultType loginType;
@Nullable
private String url;
private int uses;
private Instant lastUsed;
public MPFileSite(final MPFileUser user, final String siteName) {
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT, MPMasterKey.Version.CURRENT );
}
public MPFileSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType,
final MPMasterKey.Version algorithmVersion) {
this.user = user;
this.siteName = siteName;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.lastUsed = new Instant();
}
protected MPFileSite(final MPFileUser user, final String siteName, @Nullable final String siteContent,
final UnsignedInteger siteCounter,
final MPResultType resultType, final MPMasterKey.Version algorithmVersion,
@Nullable final String loginContent, @Nullable final MPResultType loginType,
@Nullable final String url, final int uses, final Instant lastUsed) {
this.user = user;
this.siteName = siteName;
this.siteContent = siteContent;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.loginContent = loginContent;
this.loginType = loginType;
this.url = url;
this.uses = uses;
this.lastUsed = lastUsed;
}
public String resultFor(final MPMasterKey masterKey)
throws MPInvalidatedException {
return resultFor( masterKey, MPKeyPurpose.Authentication, null );
}
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext)
throws MPInvalidatedException {
return resultFor( masterKey, keyPurpose, keyContext, getSiteContent() );
}
public String loginFor(final MPMasterKey masterKey)
throws MPInvalidatedException {
if (loginType == null)
loginType = MPResultType.GeneratedName;
return loginFor( masterKey, loginType, loginContent );
}
public MPFileUser getUser() {
return user;
}
@Override
public String getSiteName() {
return siteName;
}
@Override
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
@Nullable
public String getSiteContent() {
return siteContent;
}
public void setSitePassword(final MPMasterKey masterKey, @Nullable final MPResultType resultType, @Nullable final String result)
throws MPInvalidatedException {
this.resultType = resultType;
if (result == null)
this.siteContent = null;
else
this.siteContent = masterKey.siteState(
getSiteName(), getSiteCounter(), MPKeyPurpose.Authentication, null, getResultType(), result, getAlgorithmVersion() );
}
@Override
public UnsignedInteger getSiteCounter() {
return siteCounter;
}
@Override
public void setSiteCounter(final UnsignedInteger siteCounter) {
this.siteCounter = siteCounter;
}
@Override
public MPResultType getResultType() {
return resultType;
}
@Override
public void setResultType(final MPResultType resultType) {
this.resultType = resultType;
}
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
@Override
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
@Nullable
public MPResultType getLoginType() {
return loginType;
}
@Nullable
public String getLoginContent() {
return loginContent;
}
public void setLoginName(final MPMasterKey masterKey, @Nullable final MPResultType loginType, @Nullable final String result)
throws MPInvalidatedException {
this.loginType = loginType;
if (this.loginType != null)
if (result == null)
this.loginContent = null;
else
this.loginContent = masterKey.siteState(
siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, this.loginType, result, algorithmVersion );
}
@Nullable
public String getUrl() {
return url;
}
public void setUrl(@Nullable final String url) {
this.url = url;
}
public int getUses() {
return uses;
}
public Instant getLastUsed() {
return lastUsed;
}
public void use() {
uses++;
lastUsed = new Instant();
user.use();
}
}

View File

@ -0,0 +1,172 @@
//==============================================================================
// 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.model;
import com.google.common.collect.*;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.*;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.*;
/**
* @author lhunath, 14-12-07
*/
public class MPFileUser extends MPUser<MPFileSite> implements Comparable<MPFileUser> {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPFileUser.class );
private final String fullName;
private final Collection<MPFileSite> sites = Sets.newHashSet();
@Nullable
private byte[] keyID;
private MPMasterKey.Version algorithmVersion;
private int avatar;
private MPResultType defaultType;
private ReadableInstant lastUsed;
public MPFileUser(final String fullName) {
this( fullName, null, MPMasterKey.Version.CURRENT );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion) {
this( fullName, keyID, algorithmVersion, 0, MPResultType.DEFAULT, new Instant() );
}
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPMasterKey.Version algorithmVersion, final int avatar,
final MPResultType defaultType, final ReadableInstant lastUsed) {
this.fullName = fullName;
this.keyID = (keyID == null)? null: keyID.clone();
this.algorithmVersion = algorithmVersion;
this.avatar = avatar;
this.defaultType = defaultType;
this.lastUsed = lastUsed;
}
@Override
public String getFullName() {
return fullName;
}
@Override
public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion;
}
@Override
public int getAvatar() {
return avatar;
}
public void setAvatar(final int avatar) {
this.avatar = avatar;
}
public MPResultType getDefaultType() {
return defaultType;
}
public void setDefaultType(final MPResultType defaultType) {
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() {
return lastUsed;
}
public void use() {
lastUsed = new Instant();
}
public Iterable<MPFileSite> getSites() {
return sites;
}
@Override
public void addSite(final MPFileSite site) {
sites.add( site );
}
@Override
public void deleteSite(final MPFileSite site) {
sites.remove( site );
}
@Override
public Collection<MPFileSite> findSites(final String query) {
ImmutableList.Builder<MPFileSite> results = ImmutableList.builder();
for (final MPFileSite site : getSites())
if (site.getSiteName().startsWith( query ))
results.add( site );
return results.build();
}
/**
* Performs an authentication attempt against the keyID for this user.
*
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
*
* @param masterPassword The password to authenticate with.
*
* @return The master key for the user if authentication was successful.
*
* @throws MPIncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
*/
@Nonnull
@Override
public MPMasterKey authenticate(final char[] masterPassword)
throws MPIncorrectMasterPasswordException {
try {
key = new MPMasterKey( getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0))
keyID = key.getKeyID( algorithmVersion );
else if (!Arrays.equals( key.getKeyID( algorithmVersion ), keyID ))
throw new MPIncorrectMasterPasswordException( this );
return key;
}
catch (final MPInvalidatedException e) {
throw logger.bug( e );
}
}
void save()
throws MPInvalidatedException {
MPFileUserManager.get().save( this, getMasterKey() );
}
@Override
public int compareTo(final MPFileUser o) {
int comparison = getLastUsed().compareTo( o.getLastUsed() );
if (comparison == 0)
comparison = getFullName().compareTo( o.getFullName() );
return comparison;
}
}

View File

@ -24,20 +24,22 @@ import com.google.common.base.*;
import com.google.common.collect.*; import com.google.common.collect.*;
import com.google.common.io.CharSink; import com.google.common.io.CharSink;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.masterpassword.MPConstant; import com.lyndir.masterpassword.*;
import java.io.*; import java.io.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
* Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}. * Manages user data stored in user-specific {@code .mpsites} files under {@code .mpw.d}.
*
* @author lhunath, 14-12-07 * @author lhunath, 14-12-07
*/ */
public class MPUserFileManager extends MPUserManager { public class MPFileUserManager extends MPUserManager {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPUserFileManager.class ); private static final Logger logger = Logger.get( MPFileUserManager.class );
private static final MPUserFileManager instance; private static final MPFileUserManager instance;
static { static {
String rcDir = System.getenv( MPConstant.env_rcDir ); String rcDir = System.getenv( MPConstant.env_rcDir );
@ -49,31 +51,31 @@ public class MPUserFileManager extends MPUserManager {
private final File userFilesDirectory; private final File userFilesDirectory;
public static MPUserFileManager get() { public static MPFileUserManager get() {
MPUserManager.instance = instance; MPUserManager.instance = instance;
return instance; return instance;
} }
public static MPUserFileManager create(final File userFilesDirectory) { public static MPFileUserManager create(final File userFilesDirectory) {
return new MPUserFileManager( userFilesDirectory ); return new MPFileUserManager( userFilesDirectory );
} }
protected MPUserFileManager(final File userFilesDirectory) { protected MPFileUserManager(final File userFilesDirectory) {
super( unmarshallUsers( userFilesDirectory ) ); super( unmarshallUsers( userFilesDirectory ) );
this.userFilesDirectory = userFilesDirectory; this.userFilesDirectory = userFilesDirectory;
} }
private static Iterable<MPUser> unmarshallUsers(final File userFilesDirectory) { private static Iterable<MPFileUser> unmarshallUsers(final File userFilesDirectory) {
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) { if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory ); logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
return ImmutableList.of(); return ImmutableList.of();
} }
return FluentIterable.from( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPUser>() { return FluentIterable.from( listUserFiles( userFilesDirectory ) ).transform( new Function<File, MPFileUser>() {
@Nullable @Nullable
@Override @Override
public MPUser apply(@Nullable final File file) { public MPFileUser apply(@Nullable final File file) {
try { try {
return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) ); return new MPFlatUnmarshaller().unmarshall( Preconditions.checkNotNull( file ) );
} }
@ -95,42 +97,37 @@ public class MPUserFileManager extends MPUserManager {
} }
@Override @Override
public void addUser(final MPUser user) { public void deleteUser(final MPFileUser user) {
super.addUser( user );
save();
}
@Override
public void deleteUser(final MPUser user) {
super.deleteUser( user ); super.deleteUser( user );
save();
// Remove deleted users.
File userFile = getUserFile( user );
if (userFile.exists() && !userFile.delete())
logger.err( "Couldn't delete file: %s", userFile );
} }
/** /**
* Write the current user state to disk. * Write the current user state to disk.
*/ */
public void save() { public void save(final MPFileUser user, final MPMasterKey masterKey)
// Save existing users. throws MPInvalidatedException {
for (final MPUser user : getUsers())
try { try {
new CharSink() { new CharSink() {
@Override @Override
public Writer openStream() public Writer openStream()
throws IOException { throws IOException {
File mpsitesFile = new File( userFilesDirectory, user.getFullName() + ".mpsites" ); return new OutputStreamWriter( new FileOutputStream( getUserFile( user ) ), Charsets.UTF_8 );
return new OutputStreamWriter( new FileOutputStream( mpsitesFile ), Charsets.UTF_8 );
} }
}.write( new MPFlatMarshaller().marshall( user, null/*TODO: masterKey*/, MPMarshaller.ContentMode.PROTECTED ) ); }.write( new MPFlatMarshaller().marshall( user, masterKey, MPMarshaller.ContentMode.PROTECTED ) );
} }
catch (final IOException e) { catch (final IOException e) {
logger.err( e, "Unable to save sites for user: %s", user ); logger.err( e, "Unable to save sites for user: %s", user );
} }
}
// Remove deleted users. @Nonnull
for (final File userFile : listUserFiles( userFilesDirectory )) private File getUserFile(final MPFileUser user) {
if (getUserNamed( userFile.getName().replaceFirst( "\\.mpsites$", "" ) ) == null) return new File( userFilesDirectory, user.getFullName() + ".mpsites" );
if (!userFile.delete())
logger.err( "Couldn't delete file: %s", userFile );
} }
/** /**

View File

@ -21,8 +21,7 @@ package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse; import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.lyndir.masterpassword.MPConstant; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.MasterKey;
import org.joda.time.Instant; import org.joda.time.Instant;
@ -34,7 +33,8 @@ public class MPFlatMarshaller implements MPMarshaller {
private static final int FORMAT = 1; private static final int FORMAT = 1;
@Override @Override
public String marshall(final MPUser user, final MasterKey masterKey, final ContentMode contentMode) { public String marshall(final MPFileUser user, final MPMasterKey masterKey, final ContentMode contentMode)
throws MPInvalidatedException {
StringBuilder content = new StringBuilder(); StringBuilder content = new StringBuilder();
content.append( "# Master Password site export\n" ); content.append( "# Master Password site export\n" );
content.append( "# " ).append( contentMode.description() ).append( '\n' ); content.append( "# " ).append( contentMode.description() ).append( '\n' );
@ -46,7 +46,7 @@ public class MPFlatMarshaller implements MPMarshaller {
content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' ); content.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' ); content.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' ); content.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
content.append( "# Algorithm: " ).append( MasterKey.Version.CURRENT.toInt() ).append( '\n' ); content.append( "# Algorithm: " ).append( MPMasterKey.Version.CURRENT.toInt() ).append( '\n' );
content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' ); content.append( "# Default Type: " ).append( user.getDefaultType().getType() ).append( '\n' );
content.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' ); content.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
content.append( "##\n" ); content.append( "##\n" );
@ -54,7 +54,7 @@ public class MPFlatMarshaller implements MPMarshaller {
content.append( "# Last Times Password Login\t Site\tSite\n" ); content.append( "# Last Times Password Login\t Site\tSite\n" );
content.append( "# used used type name\t name\tpassword\n" ); content.append( "# used used type name\t name\tpassword\n" );
for (final MPSite site : user.getSites()) { for (final MPFileSite site : user.getSites()) {
String loginName = site.getLoginContent(); String loginName = site.getLoginContent();
String password = site.getSiteContent(); String password = site.getSiteContent();
if (!contentMode.isRedacted()) { if (!contentMode.isRedacted()) {

View File

@ -44,7 +44,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
@Nonnull @Nonnull
@Override @Override
public MPUser unmarshall(@Nonnull final File file) public MPFileUser unmarshall(@Nonnull final File file)
throws IOException { throws IOException {
try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) { try (Reader reader = new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 )) {
return unmarshall( CharStreams.toString( reader ) ); return unmarshall( CharStreams.toString( reader ) );
@ -53,8 +53,8 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
@Nonnull @Nonnull
@Override @Override
public MPUser unmarshall(@Nonnull final String content) { public MPFileUser unmarshall(@Nonnull final String content) {
MPUser user = null; MPFileUser user = null;
byte[] keyID = null; byte[] keyID = null;
String fullName = null; String fullName = null;
int mpVersion = 0, importFormat = 0, avatar = 0; int mpVersion = 0, importFormat = 0, avatar = 0;
@ -70,7 +70,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
headerStarted = true; headerStarted = true;
else else
// Ends the header. // Ends the header.
user = new MPUser( fullName, keyID, MasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) ); user = new MPFileUser( fullName, keyID, MPMasterKey.Version.fromInt( mpVersion ), avatar, defaultType, new DateTime( 0 ) );
// Comment. // Comment.
else if (line.startsWith( "#" )) { else if (line.startsWith( "#" )) {
@ -103,24 +103,24 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
if (!siteMatcher.matches()) if (!siteMatcher.matches())
return null; return null;
MPSite site; MPFileSite site;
switch (importFormat) { switch (importFormat) {
case 0: case 0:
site = new MPSite( user, // site = new MPFileSite( user, //
siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPSite.DEFAULT_COUNTER, siteMatcher.group( 5 ), siteMatcher.group( 6 ), MPFileSite.DEFAULT_COUNTER,
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ), colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() ); MPConstant.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
break; break;
case 1: case 1:
site = new MPSite( user, // site = new MPFileSite( user, //
siteMatcher.group( 7 ), siteMatcher.group( 8 ), siteMatcher.group( 7 ), siteMatcher.group( 8 ),
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ), UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ), MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( MPMasterKey.Version.fromInt( ConversionUtils.toIntegerNN(
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ), colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ),
siteMatcher.group( 6 ), MPResultType.GeneratedName, null, siteMatcher.group( 6 ), MPResultType.GeneratedName, null,
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),

View File

@ -21,17 +21,17 @@ package com.lyndir.masterpassword.model;
/** /**
* @author lhunath, 14-12-17 * @author lhunath, 14-12-17
*/ */
public class IncorrectMasterPasswordException extends Exception { public class MPIncorrectMasterPasswordException extends Exception {
private final MPUser user; private final MPFileUser user;
public IncorrectMasterPasswordException(final MPUser user) { public MPIncorrectMasterPasswordException(final MPFileUser user) {
super( "Incorrect master password for user: " + user.getFullName() ); super( "Incorrect master password for user: " + user.getFullName() );
this.user = user; this.user = user;
} }
public MPUser getUser() { public MPFileUser getUser() {
return user; return user;
} }
} }

View File

@ -18,7 +18,7 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MPMasterKey;
/** /**
@ -27,7 +27,7 @@ import com.lyndir.masterpassword.MasterKey;
public class MPJSONMarshaller implements MPMarshaller { public class MPJSONMarshaller implements MPMarshaller {
@Override @Override
public String marshall(final MPUser user, final MasterKey masterKey, final ContentMode contentMode) { public String marshall(final MPFileUser user, final MPMasterKey masterKey, final ContentMode contentMode) {
// TODO // TODO
return null; return null;
} }

View File

@ -30,7 +30,7 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
@Nonnull @Nonnull
@Override @Override
public MPUser unmarshall(@Nonnull final File file) public MPFileUser unmarshall(@Nonnull final File file)
throws IOException { throws IOException {
// TODO // TODO
return null; return null;
@ -38,7 +38,7 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
@Nonnull @Nonnull
@Override @Override
public MPUser unmarshall(@Nonnull final String content) { public MPFileUser unmarshall(@Nonnull final String content) {
// TODO // TODO
return null; return null;
} }

View File

@ -18,7 +18,8 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MPInvalidatedException;
import com.lyndir.masterpassword.MPMasterKey;
/** /**
@ -26,7 +27,8 @@ import com.lyndir.masterpassword.MasterKey;
*/ */
public interface MPMarshaller { public interface MPMarshaller {
String marshall(MPUser user, MasterKey masterKey, ContentMode contentMode); String marshall(MPFileUser user, MPMasterKey masterKey, ContentMode contentMode)
throws MPInvalidatedException;
enum ContentMode { enum ContentMode {
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ), PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ),

View File

@ -24,170 +24,44 @@ import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import java.util.Objects; import java.util.Objects;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.joda.time.Instant;
/** /**
* @author lhunath, 14-12-05 * @author lhunath, 14-12-16
*/ */
public class MPSite { public abstract class MPSite {
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.ONE; public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.ONE;
private final MPUser user; public abstract String getSiteName();
private String siteName;
@Nullable
private String siteContent;
private UnsignedInteger siteCounter;
private MPResultType resultType;
private MasterKey.Version algorithmVersion;
@Nullable public abstract void setSiteName(String siteName);
private String loginContent;
@Nullable
private MPResultType loginType;
@Nullable public abstract UnsignedInteger getSiteCounter();
private String url;
private int uses;
private Instant lastUsed;
public MPSite(final MPUser user, final String siteName) { public abstract void setSiteCounter(UnsignedInteger siteCounter);
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT );
public abstract MPResultType getResultType();
public abstract void setResultType(MPResultType resultType);
public abstract MPMasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(MPMasterKey.Version algorithmVersion);
public String resultFor(final MPMasterKey masterKey, final MPKeyPurpose keyPurpose, @Nullable final String keyContext,
@Nullable final String siteContent)
throws MPInvalidatedException {
return masterKey.siteResult(
getSiteName(), getSiteCounter(), keyPurpose, keyContext, getResultType(), siteContent, getAlgorithmVersion() );
} }
public MPSite(final MPUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType) { public String loginFor(final MPMasterKey masterKey, final MPResultType loginType, @Nullable final String loginContent)
this.user = user; throws MPInvalidatedException {
this.siteName = siteName;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = MasterKey.Version.CURRENT;
this.lastUsed = new Instant();
}
protected MPSite(final MPUser user, final String siteName, @Nullable final String siteContent, final UnsignedInteger siteCounter, return masterKey.siteResult(
final MPResultType resultType, final MasterKey.Version algorithmVersion, getSiteName(), DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent, getAlgorithmVersion() );
@Nullable final String loginContent, @Nullable final MPResultType loginType,
@Nullable final String url, final int uses, final Instant lastUsed) {
this.user = user;
this.siteName = siteName;
this.siteContent = siteContent;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = algorithmVersion;
this.loginContent = loginContent;
this.loginType = loginType;
this.url = url;
this.uses = uses;
this.lastUsed = lastUsed;
}
public String resultFor(final MasterKey masterKey) {
return resultFor( masterKey, MPKeyPurpose.Authentication, null );
}
public String resultFor(final MasterKey masterKey, final MPKeyPurpose purpose, @Nullable final String context) {
return masterKey.siteResult( siteName, siteCounter, purpose, context, resultType, siteContent, algorithmVersion );
}
public String loginFor(final MasterKey masterKey) {
if (loginType == null)
loginType = MPResultType.GeneratedName;
return masterKey.siteResult( siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent,
algorithmVersion );
}
public MPUser getUser() {
return user;
}
@Nullable
protected String exportContent() {
return null;
}
public MasterKey.Version getAlgorithmVersion() {
return algorithmVersion;
}
public void setAlgorithmVersion(final MasterKey.Version mpVersion) {
this.algorithmVersion = mpVersion;
}
public Instant getLastUsed() {
return lastUsed;
}
public void updateLastUsed() {
lastUsed = new Instant();
user.updateLastUsed();
}
public String getSiteName() {
return siteName;
}
public void setSiteName(final String siteName) {
this.siteName = siteName;
}
@Nullable
public String getSiteContent() {
return siteContent;
}
public MPResultType getResultType() {
return resultType;
}
public void setResultType(final MPResultType resultType) {
this.resultType = resultType;
}
public UnsignedInteger getSiteCounter() {
return siteCounter;
}
public void setSiteCounter(final UnsignedInteger siteCounter) {
this.siteCounter = siteCounter;
}
public int getUses() {
return uses;
}
public void setUses(final int uses) {
this.uses = uses;
}
@Nullable
public MPResultType getLoginType() {
return loginType;
}
@Nullable
public String getLoginContent() {
return loginContent;
}
public void setLoginName(final MasterKey masterKey, @Nullable final MPResultType loginType, @Nullable final String result) {
this.loginType = loginType;
if (this.loginType != null)
if (result == null)
this.loginContent = null;
else
this.loginContent = masterKey.siteState(
siteName, DEFAULT_COUNTER, MPKeyPurpose.Identification, null, this.loginType, result, algorithmVersion );
}
@Nullable
public String getUrl() {
return url;
}
public void setUrl(@Nullable final String url) {
this.url = url;
} }
@Override @Override
@ -202,6 +76,6 @@ public class MPSite {
@Override @Override
public String toString() { public String toString() {
return strf( "{MPSite: %s}", getSiteName() ); return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
} }
} }

View File

@ -28,13 +28,13 @@ import java.util.Objects;
*/ */
public class MPSiteResult { public class MPSiteResult {
private final MPSite site; private final MPFileSite site;
public MPSiteResult(final MPSite site) { public MPSiteResult(final MPFileSite site) {
this.site = site; this.site = site;
} }
public MPSite getSite() { public MPFileSite getSite() {
return site; return site;
} }

View File

@ -28,9 +28,9 @@ import javax.annotation.Nonnull;
public interface MPUnmarshaller { public interface MPUnmarshaller {
@Nonnull @Nonnull
MPUser unmarshall(@Nonnull File file) MPFileUser unmarshall(@Nonnull File file)
throws IOException; throws IOException;
@Nonnull @Nonnull
MPUser unmarshall(@Nonnull String content); MPFileUser unmarshall(@Nonnull String content);
} }

View File

@ -18,155 +18,69 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.collect.ImmutableList; import com.google.common.base.Preconditions;
import com.google.common.collect.Sets;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPResultType; import com.lyndir.masterpassword.MPInvalidatedException;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MPMasterKey;
import java.util.*; import java.util.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import org.joda.time.*;
/** /**
* @author lhunath, 14-12-07 * @author lhunath, 2014-06-08
*/ */
public class MPUser implements Comparable<MPUser> { public abstract class MPUser<S extends MPSite> {
private final String fullName;
private final Collection<MPSite> sites = Sets.newHashSet();
@Nullable @Nullable
private byte[] keyID; protected MPMasterKey key;
private MasterKey.Version algorithmVersion;
private int avatar; public abstract String getFullName();
private MPResultType defaultType;
private ReadableInstant lastUsed;
public MPUser(final String fullName) { public boolean isMasterKeyAvailable() {
this( fullName, null, MasterKey.Version.CURRENT ); return key != null;
} }
public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion) {
this( fullName, keyID, algorithmVersion, 0, MPResultType.DEFAULT, new Instant() );
}
public MPUser(final String fullName, @Nullable final byte[] keyID, final MasterKey.Version algorithmVersion, final int avatar,
final MPResultType defaultType, final ReadableInstant lastUsed) {
this.fullName = fullName;
this.keyID = (keyID == null)? null: keyID.clone();
this.algorithmVersion = algorithmVersion;
this.avatar = avatar;
this.defaultType = defaultType;
this.lastUsed = lastUsed;
}
public Collection<MPSiteResult> findSitesByName(final String query) {
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
for (final MPSite site : getSites())
if (site.getSiteName().startsWith( query ))
results.add( new MPSiteResult( site ) );
return results.build();
}
public void addSite(final MPSite site) {
sites.add( site );
}
public void deleteSite(final MPSite site) {
sites.remove( site );
}
public String getFullName() {
return fullName;
}
public boolean hasKeyID() {
return keyID != null;
}
public String exportKeyID() {
return CodeUtils.encodeHex( keyID );
}
/**
* Performs an authentication attempt against the keyID for this user.
*
* Note: If this user doesn't have a keyID set yet, authentication will always succeed and the key ID will be set as a result.
*
* @param masterPassword The password to authenticate with.
*
* @return The master key for the user if authentication was successful.
*
* @throws IncorrectMasterPasswordException If authentication fails due to the given master password not matching the user's keyID.
*/
@Nonnull @Nonnull
@SuppressWarnings("MethodCanBeVariableArityMethod") public MPMasterKey getMasterKey() {
public MasterKey authenticate(final char[] masterPassword) return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
throws IncorrectMasterPasswordException {
MasterKey masterKey = new MasterKey( getFullName(), masterPassword );
if ((keyID == null) || (keyID.length == 0))
keyID = masterKey.getKeyID( algorithmVersion );
else if (!Arrays.equals( masterKey.getKeyID( algorithmVersion ), keyID ))
throw new IncorrectMasterPasswordException( this );
return masterKey;
} }
public String exportKeyID()
throws MPInvalidatedException {
return CodeUtils.encodeHex( getMasterKey().getKeyID( getAlgorithmVersion() ) );
}
public abstract MPMasterKey.Version getAlgorithmVersion();
public int getAvatar() { public int getAvatar() {
return avatar; return 0;
} }
public void setAvatar(final int avatar) { public abstract void addSite(S site);
this.avatar = avatar;
}
public MPResultType getDefaultType() { public abstract void deleteSite(S site);
return defaultType;
}
public void setDefaultType(final MPResultType defaultType) { public abstract Collection<S> findSites(String query);
this.defaultType = defaultType;
}
public ReadableInstant getLastUsed() { @Nonnull
return lastUsed; public abstract MPMasterKey authenticate(char[] masterPassword)
} throws MPIncorrectMasterPasswordException;
public void updateLastUsed() { @Override
lastUsed = new Instant(); public int hashCode() {
} return Objects.hashCode( getFullName() );
public Iterable<MPSite> getSites() {
return sites;
} }
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( fullName, ((MPUser) obj).fullName )); return (this == obj) || ((obj instanceof MPUser) && Objects.equals( getFullName(), ((MPUser<?>) obj).getFullName() ));
}
@Override
public int hashCode() {
return Objects.hashCode( fullName );
} }
@Override @Override
public String toString() { public String toString() {
return strf( "{MPUser: %s}", fullName ); return strf( "{%s: %s}", getClass().getSimpleName(), getFullName() );
}
@Override
public int compareTo(final MPUser o) {
int comparison = lastUsed.compareTo( o.lastUsed );
if (comparison == 0)
comparison = fullName.compareTo( o.fullName );
return comparison;
} }
} }

View File

@ -19,6 +19,7 @@
package com.lyndir.masterpassword.model; package com.lyndir.masterpassword.model;
import com.google.common.collect.*; import com.google.common.collect.*;
import com.lyndir.masterpassword.MPInvalidatedException;
import java.util.*; import java.util.*;
@ -27,31 +28,31 @@ import java.util.*;
*/ */
public abstract class MPUserManager { public abstract class MPUserManager {
private final Map<String, MPUser> usersByName = Maps.newHashMap(); private final Map<String, MPFileUser> usersByName = Maps.newHashMap();
static MPUserManager instance; static MPUserManager instance;
public static MPUserManager get() { public static MPUserManager get() {
return instance; return instance;
} }
protected MPUserManager(final Iterable<MPUser> users) { protected MPUserManager(final Iterable<MPFileUser> users) {
for (final MPUser user : users) for (final MPFileUser user : users)
usersByName.put( user.getFullName(), user ); usersByName.put( user.getFullName(), user );
} }
public SortedSet<MPUser> getUsers() { public SortedSet<MPFileUser> getUsers() {
return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() ); return FluentIterable.from( usersByName.values() ).toSortedSet( Ordering.natural() );
} }
public MPUser getUserNamed(final String fullName) { public MPFileUser getUserNamed(final String fullName) {
return usersByName.get( fullName ); return usersByName.get( fullName );
} }
public void addUser(final MPUser user) { public void addUser(final MPFileUser user) {
usersByName.put( user.getFullName(), user ); usersByName.put( user.getFullName(), user );
} }
public void deleteUser(final MPUser user) { public void deleteUser(final MPFileUser user) {
usersByName.remove( user.getFullName() ); usersByName.remove( user.getFullName() );
} }
} }

View File

@ -23,12 +23,10 @@ import com.google.common.collect.Lists;
import com.google.common.primitives.UnsignedInteger; 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.ConversionUtils; import com.lyndir.lhunath.opal.system.util.ConversionUtils;
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
import java.io.IOException; import java.io.IOException;
import java.util.Deque; import java.util.Deque;
import java.util.List; import java.util.List;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import javax.xml.parsers.*; import javax.xml.parsers.*;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
@ -38,7 +36,7 @@ import org.xml.sax.ext.DefaultHandler2;
/** /**
* @author lhunath, 2015-12-22 * @author lhunath, 2015-12-22
*/ */
@SuppressWarnings("HardCodedStringLiteral") @SuppressWarnings({ "HardCodedStringLiteral", "ProhibitedExceptionDeclared" })
public class MPTestSuite implements Callable<Boolean> { public class MPTestSuite implements Callable<Boolean> {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
@ -134,7 +132,8 @@ public class MPTestSuite implements Callable<Boolean> {
return tests; return tests;
} }
public boolean forEach(final String testName, final NNFunctionNN<MPTests.Case, Boolean> testFunction) { public boolean forEach(final String testName, final TestCase testFunction)
throws Exception {
List<MPTests.Case> cases = tests.getCases(); List<MPTests.Case> cases = tests.getCases();
for (int c = 0; c < cases.size(); c++) { for (int c = 0; c < cases.size(); c++) {
MPTests.Case testCase = cases.get( c ); MPTests.Case testCase = cases.get( c );
@ -144,7 +143,7 @@ public class MPTestSuite implements Callable<Boolean> {
progress( Logger.Target.INFO, c, cases.size(), // progress( Logger.Target.INFO, c, cases.size(), //
"[%s] on %s...", testName, testCase.getIdentifier() ); "[%s] on %s...", testName, testCase.getIdentifier() );
if (!testFunction.apply( testCase )) { if (!testFunction.run( testCase )) {
progress( Logger.Target.ERROR, cases.size(), cases.size(), // progress( Logger.Target.ERROR, cases.size(), cases.size(), //
"[%s] on %s: FAILED!", testName, testCase.getIdentifier() ); "[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
@ -168,11 +167,11 @@ public class MPTestSuite implements Callable<Boolean> {
@Override @Override
public Boolean call() public Boolean call()
throws Exception { throws Exception {
return forEach( "mpw", new NNFunctionNN<MPTests.Case, Boolean>() { return forEach( "mpw", new TestCase() {
@Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public boolean run(final MPTests.Case testCase)
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() ); throws Exception {
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword() );
String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(), testCase.getKeyContext(), testCase.getResultType(),
null, testCase.getAlgorithm() ); null, testCase.getAlgorithm() );
@ -196,4 +195,11 @@ public class MPTestSuite implements Callable<Boolean> {
void progress(int current, int max, String messageFormat, Object... args); void progress(int current, int max, String messageFormat, Object... args);
} }
public interface TestCase {
boolean run(MPTests.Case testCase)
throws Exception;
}
} }

View File

@ -171,8 +171,8 @@ public class MPTests {
} }
@Nonnull @Nonnull
public MasterKey.Version getAlgorithm() { public MPMasterKey.Version getAlgorithm() {
return MasterKey.Version.fromInt( checkNotNull( algorithm ) ); return MPMasterKey.Version.fromInt( checkNotNull( algorithm ) );
} }
@Nonnull @Nonnull

View File

@ -1,279 +0,0 @@
<tests>
<!-- Default values for all parameters. -->
<case id="default">
<algorithm>-1</algorithm>
<fullName>Robert Lee Mitchell</fullName>
<masterPassword>banana colored duckling</masterPassword>
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
<siteName>masterpasswordapp.com</siteName>
<siteCounter>1</siteCounter>
<resultType>Long</resultType>
<keyPurpose>Authentication</keyPurpose>
<result><!-- abstract --></result>
</case>
<!-- Algorithm 3 -->
<case id="v3" parent="default">
<algorithm>3</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v3_mb_fullName" parent="v3">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>NopaDajh8=Fene</result>
</case>
<case id="v3_mb_masterPassword" parent="v3">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v3_mb_siteName" parent="v3">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="v3_loginName" parent="v3">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>wohzaqage</result>
</case>
<case id="v3_securityAnswer" parent="v3">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v3_securityAnswer_context" parent="v3_securityAnswer">
<keyContext>question</keyContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v3_type_maximum" parent="v3">
<resultType>Maximum</resultType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v3_type_medium" parent="v3">
<resultType>Medium</resultType>
<result>Jej2$Quv</result>
</case>
<case id="v3_type_basic" parent="v3">
<resultType>Basic</resultType>
<result>WAo2xIg6</result>
</case>
<case id="v3_type_short" parent="v3">
<resultType>Short</resultType>
<result>Jej2</result>
</case>
<case id="v3_type_pin" parent="v3">
<resultType>PIN</resultType>
<result>7662</result>
</case>
<case id="v3_type_name" parent="v3">
<resultType>Name</resultType>
<result>jejraquvo</result>
</case>
<case id="v3_type_phrase" parent="v3">
<resultType>Phrase</resultType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v3_counter_ceiling" parent="v3">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 2 -->
<case id="v2" parent="default">
<algorithm>2</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v2_mb_fullName" parent="v2">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v2_mb_masterPassword" parent="v2">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v2_mb_siteName" parent="v2">
<siteName></siteName>
<result>LiheCuwhSerz6)</result>
</case>
<case id="v2_loginName" parent="v2">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>wohzaqage</result>
</case>
<case id="v2_securityAnswer" parent="v2">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v2_securityAnswer_context" parent="v2_securityAnswer">
<keyContext>question</keyContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v2_type_maximum" parent="v2">
<resultType>Maximum</resultType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v2_type_medium" parent="v2">
<resultType>Medium</resultType>
<result>Jej2$Quv</result>
</case>
<case id="v2_type_basic" parent="v2">
<resultType>Basic</resultType>
<result>WAo2xIg6</result>
</case>
<case id="v2_type_short" parent="v2">
<resultType>Short</resultType>
<result>Jej2</result>
</case>
<case id="v2_type_pin" parent="v2">
<resultType>PIN</resultType>
<result>7662</result>
</case>
<case id="v2_type_name" parent="v2">
<resultType>Name</resultType>
<result>jejraquvo</result>
</case>
<case id="v2_type_phrase" parent="v2">
<resultType>Phrase</resultType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v2_counter_ceiling" parent="v2">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 1 -->
<case id="v1" parent="default">
<algorithm>1</algorithm>
<result>Jejr5[RepuSosp</result>
</case>
<case id="v1_mb_fullName" parent="v1">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>WaqoGuho2[Xaxw</result>
</case>
<case id="v1_mb_masterPassword" parent="v1">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>QesuHirv5-Xepl</result>
</case>
<case id="v1_mb_siteName" parent="v1">
<siteName></siteName>
<result>WawiYarp2@Kodh</result>
</case>
<case id="v1_loginName" parent="v1">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>wohzaqage</result>
</case>
<case id="v1_securityAnswer" parent="v1">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>xin diyjiqoja hubu</result>
</case>
<case id="v1_securityAnswer_context" parent="v1_securityAnswer">
<keyContext>question</keyContext>
<result>xogx tem cegyiva jab</result>
</case>
<case id="v1_type_maximum" parent="v1">
<resultType>Maximum</resultType>
<result>W6@692^B1#&amp;@gVdSdLZ@</result>
</case>
<case id="v1_type_medium" parent="v1">
<resultType>Medium</resultType>
<result>Jej2$Quv</result>
</case>
<case id="v1_type_basic" parent="v1">
<resultType>Basic</resultType>
<result>WAo2xIg6</result>
</case>
<case id="v1_type_short" parent="v1">
<resultType>Short</resultType>
<result>Jej2</result>
</case>
<case id="v1_type_pin" parent="v1">
<resultType>PIN</resultType>
<result>7662</result>
</case>
<case id="v1_type_name" parent="v1">
<resultType>Name</resultType>
<result>jejraquvo</result>
</case>
<case id="v1_type_phrase" parent="v1">
<resultType>Phrase</resultType>
<result>jejr quv cabsibu tam</result>
</case>
<case id="v1_counter_ceiling" parent="v1">
<siteCounter>4294967295</siteCounter>
<result>XambHoqo6[Peni</result>
</case>
<!-- Algorithm 0 -->
<case id="v0" parent="default">
<algorithm>0</algorithm>
<result>Feji5@ReduWosh</result>
</case>
<case id="v0_mb_fullName" parent="v0">
<fullName></fullName>
<keyID>1717AA1F9BF5BA56CD0965CDA3D78E6D2E6A1EA8C067A8EA621F3DDAD4A87EB8</keyID>
<result>HajrYudo7@Mamh</result>
</case>
<case id="v0_mb_masterPassword" parent="v0">
<masterPassword></masterPassword>
<keyID>351432B8528A5ABECAB768CA95015097DE76FE14C41E10AF36C67DCFB8917E08</keyID>
<result>MewmDini0]Meho</result>
</case>
<case id="v0_mb_siteName" parent="v0">
<siteName></siteName>
<result>HahiVana2@Nole</result>
</case>
<case id="v0_loginName" parent="v0">
<keyPurpose>Identification</keyPurpose>
<resultType>Name</resultType>
<result>lozwajave</result>
</case>
<case id="v0_securityAnswer" parent="v0">
<keyPurpose>Recovery</keyPurpose>
<resultType>Phrase</resultType>
<result>miy lirfijoja dubu</result>
</case>
<case id="v0_securityAnswer_context" parent="v0_securityAnswer">
<keyContext>question</keyContext>
<result>movm bex gevrica jaf</result>
</case>
<case id="v0_type_maximum" parent="v0">
<resultType>Maximum</resultType>
<result>w1!3bA3icmRAc)SS@lwl</result>
</case>
<case id="v0_type_medium" parent="v0">
<resultType>Medium</resultType>
<result>Fej7]Jug</result>
</case>
<case id="v0_type_basic" parent="v0">
<resultType>Basic</resultType>
<result>wvH7irC1</result>
</case>
<case id="v0_type_short" parent="v0">
<resultType>Short</resultType>
<result>Fej7</result>
</case>
<case id="v0_type_pin" parent="v0">
<resultType>PIN</resultType>
<result>2117</result>
</case>
<case id="v0_type_name" parent="v0">
<resultType>Name</resultType>
<result>fejrajugo</result>
</case>
<case id="v0_type_phrase" parent="v0">
<resultType>Phrase</resultType>
<result>fejr jug gabsibu bax</result>
</case>
<case id="v0_counter_ceiling" parent="v0">
<siteCounter>4294967295</siteCounter>
<result>QateDojh1@Hecn</result>
</case>
</tests>

View File

@ -0,0 +1 @@
../../../../../mpw_tests.xml

View File

@ -22,17 +22,15 @@ import static org.testng.Assert.*;
import com.lyndir.lhunath.opal.system.CodeUtils; import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
import javax.annotation.Nonnull;
import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NonNls;
import org.testng.annotations.BeforeMethod; import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test; import org.testng.annotations.Test;
public class MasterKeyTest { public class MPMasterKeyTest {
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MasterKeyTest.class ); private static final Logger logger = Logger.get( MPMasterKeyTest.class );
@NonNls @NonNls
private MPTestSuite testSuite; private MPTestSuite testSuite;
@ -48,11 +46,11 @@ public class MasterKeyTest {
public void testEncode() public void testEncode()
throws Exception { throws Exception {
testSuite.forEach( "testEncode", new NNFunctionNN<MPTests.Case, Boolean>() { testSuite.forEach( "testEncode", new MPTestSuite.TestCase() {
@Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public boolean run(final MPTests.Case testCase)
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() ); throws Exception {
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( assertEquals(
masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(), masterKey.siteResult( testCase.getSiteName(), testCase.getSiteCounter(), testCase.getKeyPurpose(),
@ -71,7 +69,7 @@ public class MasterKeyTest {
MPTests.Case defaultCase = testSuite.getTests().getDefaultCase(); MPTests.Case defaultCase = testSuite.getTests().getDefaultCase();
assertEquals( new MasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(), assertEquals( new MPMasterKey( defaultCase.getFullName(), defaultCase.getMasterPassword() ).getFullName(),
defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase ); defaultCase.getFullName(), "[testGetUserName] Failed test case: " + defaultCase );
} }
@ -79,11 +77,11 @@ public class MasterKeyTest {
public void testGetKeyID() public void testGetKeyID()
throws Exception { throws Exception {
testSuite.forEach( "testGetKeyID", new NNFunctionNN<MPTests.Case, Boolean>() { testSuite.forEach( "testGetKeyID", new MPTestSuite.TestCase() {
@Nonnull
@Override @Override
public Boolean apply(@Nonnull final MPTests.Case testCase) { public boolean run(final MPTests.Case testCase)
MasterKey masterKey = new MasterKey( testCase.getFullName(), testCase.getMasterPassword() ); throws Exception {
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword() );
assertEquals( CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ), assertEquals( CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase ); testCase.getKeyID(), "[testGetKeyID] Failed test case: " + testCase );

View File

@ -54,9 +54,9 @@ public class EmergencyActivity extends Activity {
private final Preferences preferences = Preferences.get( this ); private final Preferences preferences = Preferences.get( this );
private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() ); private final ListeningExecutorService executor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) ); private final ImmutableList<MPResultType> allResultTypes = ImmutableList.copyOf( MPResultType.forClass( MPResultTypeClass.Template ) );
private final ImmutableList<MasterKey.Version> allVersions = ImmutableList.copyOf( MasterKey.Version.values() ); private final ImmutableList<MPMasterKey.Version> allVersions = ImmutableList.copyOf( MPMasterKey.Version.values() );
private MasterKey masterKey; private MPMasterKey masterKey;
@BindView(R.id.progressView) @BindView(R.id.progressView)
ProgressBar progressView; ProgressBar progressView;
@ -154,7 +154,7 @@ public class EmergencyActivity extends Activity {
@Override @Override
public void onClick(final View v) { public void onClick(final View v) {
@SuppressWarnings("SuspiciousMethodCalls") @SuppressWarnings("SuspiciousMethodCalls")
MasterKey.Version siteVersion = MPMasterKey.Version siteVersion =
allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() ); allVersions.get( (allVersions.indexOf( siteVersionButton.getTag() ) + 1) % allVersions.size() );
preferences.setDefaultVersion( siteVersion ); preferences.setDefaultVersion( siteVersion );
siteVersionButton.setTag( siteVersion ); siteVersionButton.setTag( siteVersion );
@ -221,7 +221,7 @@ public class EmergencyActivity extends Activity {
MPResultType defaultResultType = preferences.getDefaultResultType(); MPResultType defaultResultType = preferences.getDefaultResultType();
resultTypeButton.setTag( defaultResultType ); resultTypeButton.setTag( defaultResultType );
resultTypeButton.setText( defaultResultType.getShortName() ); resultTypeButton.setText( defaultResultType.getShortName() );
MasterKey.Version defaultVersion = preferences.getDefaultVersion(); MPMasterKey.Version defaultVersion = preferences.getDefaultVersion();
siteVersionButton.setTag( defaultVersion ); siteVersionButton.setTag( defaultVersion );
siteVersionButton.setText( defaultVersion.name() ); siteVersionButton.setText( defaultVersion.name() );
siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) ); siteCounterButton.setText( MessageFormat.format( "{0}", 1 ) );
@ -275,7 +275,7 @@ public class EmergencyActivity extends Activity {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.VISIBLE ); progressView.setVisibility( View.VISIBLE );
masterKey = new MasterKey( fullName, masterPassword ); masterKey = new MPMasterKey( fullName, masterPassword );
updateSitePassword(); updateSitePassword();
} }
@ -283,7 +283,7 @@ public class EmergencyActivity extends Activity {
final String siteName = siteNameField.getText().toString(); final String siteName = siteNameField.getText().toString();
final MPResultType type = (MPResultType) resultTypeButton.getTag(); final MPResultType type = (MPResultType) resultTypeButton.getTag();
final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() ); final UnsignedInteger counter = UnsignedInteger.valueOf( siteCounterButton.getText().toString() );
final MasterKey.Version version = (MasterKey.Version) siteVersionButton.getTag(); final MPMasterKey.Version version = (MPMasterKey.Version) siteVersionButton.getTag();
if ((masterKey == null) || siteName.isEmpty() || (type == null)) { if ((masterKey == null) || siteName.isEmpty() || (type == null)) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
@ -310,6 +310,10 @@ public class EmergencyActivity extends Activity {
} }
} ); } );
} }
catch (final MPInvalidatedException ignored) {
sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE );
}
catch (final RuntimeException e) { catch (final RuntimeException e) {
sitePasswordField.setText( "" ); sitePasswordField.setText( "" );
progressView.setVisibility( View.INVISIBLE ); progressView.setVisibility( View.INVISIBLE );

View File

@ -151,7 +151,7 @@ public final class Preferences {
return MPResultType.values()[prefs().getInt( PREF_RESULT_TYPE, MPResultType.DEFAULT.ordinal() )]; return MPResultType.values()[prefs().getInt( PREF_RESULT_TYPE, MPResultType.DEFAULT.ordinal() )];
} }
public boolean setDefaultVersion(final MasterKey.Version value) { public boolean setDefaultVersion(final MPMasterKey.Version value) {
if (getDefaultVersion() == value) if (getDefaultVersion() == value)
return false; return false;
@ -160,7 +160,7 @@ public final class Preferences {
} }
@Nonnull @Nonnull
public MasterKey.Version getDefaultVersion() { public MPMasterKey.Version getDefaultVersion() {
return MasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MasterKey.Version.CURRENT.ordinal() )]; return MPMasterKey.Version.values()[prefs().getInt( PREF_ALGORITHM_VERSION, MPMasterKey.Version.CURRENT.ordinal() )];
} }
} }

View File

@ -1 +1 @@
../../core/java/tests/src/main/resources/mpw_tests.xml ../../core/mpw_tests.xml

View File

@ -24,7 +24,6 @@ import com.google.common.io.*;
import com.lyndir.lhunath.opal.system.logging.Logger; import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.TypeUtils; import com.lyndir.lhunath.opal.system.util.TypeUtils;
import com.lyndir.masterpassword.gui.model.User;
import com.lyndir.masterpassword.gui.view.PasswordFrame; import com.lyndir.masterpassword.gui.view.PasswordFrame;
import com.lyndir.masterpassword.gui.view.UnlockFrame; import com.lyndir.masterpassword.gui.view.UnlockFrame;
import java.io.*; import java.io.*;
@ -46,7 +45,7 @@ public class GUI implements UnlockFrame.SignInCallback {
private static final Logger logger = Logger.get( GUI.class ); private static final Logger logger = Logger.get( GUI.class );
private final UnlockFrame unlockFrame = new UnlockFrame( this ); private final UnlockFrame unlockFrame = new UnlockFrame( this );
private PasswordFrame passwordFrame; private PasswordFrame<?, ?> passwordFrame;
public static void main(final String... args) { public static void main(final String... args) {
@ -104,12 +103,8 @@ public class GUI implements UnlockFrame.SignInCallback {
} }
@Override @Override
public void signedIn(final User user) { public void signedIn(final PasswordFrame<?, ?> passwordFrame) {
passwordFrame = newPasswordFrame( user ); this.passwordFrame = passwordFrame;
open(); open();
} }
protected PasswordFrame newPasswordFrame(final User user) {
return new PasswordFrame( user );
}
} }

View File

@ -20,21 +20,22 @@ package com.lyndir.masterpassword.gui.model;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPResultType; import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MPMasterKey;
import com.lyndir.masterpassword.model.MPSite;
/** /**
* @author lhunath, 14-12-16 * @author lhunath, 14-12-16
*/ */
public class IncognitoSite extends Site { public class IncognitoSite extends MPSite {
private String siteName; private String siteName;
private UnsignedInteger siteCounter; private UnsignedInteger siteCounter;
private MPResultType resultType; private MPResultType resultType;
private MasterKey.Version algorithmVersion; private MPMasterKey.Version algorithmVersion;
public IncognitoSite(final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType, public IncognitoSite(final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType,
final MasterKey.Version algorithmVersion) { final MPMasterKey.Version algorithmVersion) {
this.siteName = siteName; this.siteName = siteName;
this.siteCounter = siteCounter; this.siteCounter = siteCounter;
this.resultType = resultType; this.resultType = resultType;
@ -62,12 +63,12 @@ public class IncognitoSite extends Site {
} }
@Override @Override
public MasterKey.Version getAlgorithmVersion() { public MPMasterKey.Version getAlgorithmVersion() {
return algorithmVersion; return algorithmVersion;
} }
@Override @Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) { public void setAlgorithmVersion(final MPMasterKey.Version algorithmVersion) {
this.algorithmVersion = algorithmVersion; this.algorithmVersion = algorithmVersion;
} }

View File

@ -19,15 +19,17 @@
package com.lyndir.masterpassword.gui.model; package com.lyndir.masterpassword.gui.model;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.MasterKey; import com.lyndir.masterpassword.MPMasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException; import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
import javax.annotation.Nullable; import com.lyndir.masterpassword.model.MPUser;
import java.util.Collection;
import javax.annotation.Nonnull;
/** /**
* @author lhunath, 2014-06-08 * @author lhunath, 2014-06-08
*/ */
public class IncognitoUser extends User { public class IncognitoUser extends MPUser<IncognitoSite> {
private final String fullName; private final String fullName;
@ -41,21 +43,27 @@ public class IncognitoUser extends User {
} }
@Override @Override
public void authenticate(final char[] masterPassword) public MPMasterKey.Version getAlgorithmVersion() {
throws IncorrectMasterPasswordException { return MPMasterKey.Version.CURRENT;
this.key = new MasterKey( getFullName(), masterPassword );
} }
@Override @Override
public Iterable<Site> findSitesByName(final String siteName) { public void addSite(final IncognitoSite site) {
}
@Override
public void deleteSite(final IncognitoSite site) {
}
@Override
public Collection<IncognitoSite> findSites(final String query) {
return ImmutableList.of(); return ImmutableList.of();
} }
@Nonnull
@Override @Override
public void addSite(final Site site) { public MPMasterKey authenticate(final char[] masterPassword)
} throws MPIncorrectMasterPasswordException {
return key = new MPMasterKey( getFullName(), masterPassword );
@Override
public void deleteSite(final Site site) {
} }
} }

View File

@ -1,96 +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.model;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.*;
/**
* @author lhunath, 14-12-16
*/
public class ModelSite extends Site {
private final MPSite model;
public ModelSite(final MPSiteResult result) {
model = result.getSite();
}
public MPSite getModel() {
return model;
}
@Override
public String getSiteName() {
return model.getSiteName();
}
@Override
public void setSiteName(final String siteName) {
model.setSiteName( siteName );
MPUserFileManager.get().save();
}
@Override
public MPResultType getResultType() {
return model.getResultType();
}
@Override
public void setResultType(final MPResultType resultType) {
if (resultType != getResultType()) {
model.setResultType( resultType );
MPUserFileManager.get().save();
}
}
@Override
public MasterKey.Version getAlgorithmVersion() {
return model.getAlgorithmVersion();
}
@Override
public void setAlgorithmVersion(final MasterKey.Version algorithmVersion) {
if (algorithmVersion != getAlgorithmVersion()) {
model.setAlgorithmVersion( algorithmVersion );
MPUserFileManager.get().save();
}
}
@Override
public UnsignedInteger getSiteCounter() {
return model.getSiteCounter();
}
@Override
public void setSiteCounter(final UnsignedInteger siteCounter) {
if (siteCounter.equals( getSiteCounter() )) {
model.setSiteCounter( siteCounter );
MPUserFileManager.get().save();
}
}
public void use() {
model.updateLastUsed();
MPUserFileManager.get().save();
}
}

View File

@ -1,97 +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.model;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.FluentIterable;
import com.lyndir.masterpassword.gui.*;
import com.lyndir.masterpassword.model.*;
import java.util.Arrays;
import javax.annotation.Nullable;
/**
* @author lhunath, 14-12-08
*/
public class ModelUser extends User {
private final MPUser model;
public ModelUser(final MPUser model) {
this.model = model;
}
public MPUser getModel() {
return model;
}
@Override
public String getFullName() {
return model.getFullName();
}
@Override
public int getAvatar() {
return model.getAvatar();
}
public void setAvatar(final int avatar) {
model.setAvatar( avatar % Res.avatars());
MPUserFileManager.get().save();
}
@Override
public void authenticate(final char[] masterPassword)
throws IncorrectMasterPasswordException {
key = model.authenticate( masterPassword );
MPUserFileManager.get().save();
}
@Override
public Iterable<Site> findSitesByName(final String siteName) {
return FluentIterable.from( model.findSitesByName( siteName ) ).transform( new Function<MPSiteResult, Site>() {
@Nullable
@Override
public Site apply(@Nullable final MPSiteResult site) {
return new ModelSite( Preconditions.checkNotNull( site ) );
}
} );
}
@Override
public void addSite(final Site site) {
model.addSite( new MPSite( model, site.getSiteName(), site.getSiteCounter(), site.getResultType() ) );
model.updateLastUsed();
MPUserFileManager.get().save();
}
@Override
public void deleteSite(final Site site) {
if (site instanceof ModelSite) {
model.deleteSite(((ModelSite) site).getModel());
MPUserFileManager.get().save();
}
}
public boolean keySaved() {
// TODO
return false;
}
}

View File

@ -1,53 +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.model;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.MasterKey;
/**
* @author lhunath, 14-12-16
*/
public abstract class Site {
public abstract String getSiteName();
public abstract void setSiteName(String siteName);
public abstract MPResultType getResultType();
public abstract void setResultType(MPResultType resultType);
public abstract MasterKey.Version getAlgorithmVersion();
public abstract void setAlgorithmVersion(MasterKey.Version algorithmVersion);
public abstract UnsignedInteger getSiteCounter();
public abstract void setSiteCounter(UnsignedInteger siteCounter);
@Override
public String toString() {
return strf( "{%s: %s}", getClass().getSimpleName(), getSiteName() );
}
}

View File

@ -1,76 +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.model;
import com.google.common.base.Preconditions;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* @author lhunath, 2014-06-08
*/
public abstract class User {
@Nullable
protected MasterKey key;
public abstract String getFullName();
@SuppressWarnings("MethodCanBeVariableArityMethod")
public abstract void authenticate(char[] masterPassword)
throws IncorrectMasterPasswordException;
public int getAvatar() {
return 0;
}
public boolean isKeyAvailable() {
return key != null;
}
@Nonnull
public MasterKey getKey() {
return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
}
public abstract Iterable<Site> findSitesByName(String siteName);
public abstract void addSite(Site site);
public abstract void deleteSite(Site site);
@Override
public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof User) && Objects.equals( getFullName(), ((User) obj).getFullName() ));
}
@Override
public int hashCode() {
return Objects.hashCode( getFullName() );
}
@Override
public String toString() {
return getFullName();
}
}

View File

@ -21,7 +21,7 @@ package com.lyndir.masterpassword.gui.platform.mac;
import com.apple.eawt.*; import com.apple.eawt.*;
import com.lyndir.masterpassword.gui.GUI; import com.lyndir.masterpassword.gui.GUI;
import com.lyndir.masterpassword.gui.view.PasswordFrame; import com.lyndir.masterpassword.gui.view.PasswordFrame;
import com.lyndir.masterpassword.gui.model.User; import com.lyndir.masterpassword.model.MPUser;
import javax.swing.*; import javax.swing.*;
@ -52,12 +52,4 @@ public class AppleGUI extends GUI {
} }
} ); } );
} }
@Override
protected PasswordFrame newPasswordFrame(final User user) {
PasswordFrame frame = super.newPasswordFrame( user );
frame.setDefaultCloseOperation( WindowConstants.HIDE_ON_CLOSE );
return frame;
}
} }

View File

@ -20,9 +20,8 @@ package com.lyndir.masterpassword.gui.view;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.model.User; import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.gui.view.UnlockFrame;
import java.awt.*; import java.awt.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -32,7 +31,7 @@ import javax.swing.*;
/** /**
* @author lhunath, 2014-06-11 * @author lhunath, 2014-06-11
*/ */
public abstract class AuthenticationPanel extends Components.GradientPanel { public abstract class AuthenticationPanel<U extends MPUser<?>> extends Components.GradientPanel {
protected final UnlockFrame unlockFrame; protected final UnlockFrame unlockFrame;
protected final JLabel avatarLabel; protected final JLabel avatarLabel;
@ -65,11 +64,12 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
} }
@Nullable @Nullable
protected abstract User getSelectedUser(); protected abstract U getSelectedUser();
@Nonnull @Nonnull
public abstract char[] getMasterPassword(); public abstract char[] getMasterPassword();
@Nullable
public Component getFocusComponent() { public Component getFocusComponent() {
return null; return null;
} }
@ -79,4 +79,6 @@ public abstract class AuthenticationPanel extends Components.GradientPanel {
} }
public abstract void reset(); public abstract void reset();
public abstract PasswordFrame<?, ?> newPasswordFrame();
} }

View File

@ -18,9 +18,12 @@
package com.lyndir.masterpassword.gui.view; package com.lyndir.masterpassword.gui.view;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.masterpassword.MPMasterKey;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.model.IncognitoSite;
import com.lyndir.masterpassword.gui.model.IncognitoUser; import com.lyndir.masterpassword.gui.model.IncognitoUser;
import com.lyndir.masterpassword.gui.model.User;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@ -34,7 +37,8 @@ import javax.swing.event.DocumentListener;
/** /**
* @author lhunath, 2014-06-11 * @author lhunath, 2014-06-11
*/ */
public class IncognitoAuthenticationPanel extends AuthenticationPanel implements DocumentListener, ActionListener { @SuppressWarnings({ "serial", "MagicNumber" })
public class IncognitoAuthenticationPanel extends AuthenticationPanel<IncognitoUser> implements DocumentListener, ActionListener {
private final JTextField fullNameField; private final JTextField fullNameField;
private final JPasswordField masterPasswordField; private final JPasswordField masterPasswordField;
@ -76,7 +80,19 @@ public class IncognitoAuthenticationPanel extends AuthenticationPanel implements
} }
@Override @Override
protected User getSelectedUser() { public PasswordFrame<IncognitoUser, ?> newPasswordFrame() {
return new PasswordFrame<IncognitoUser, IncognitoSite>(getSelectedUser()) {
@Override
protected IncognitoSite createSite(final IncognitoUser user, final String siteName, final UnsignedInteger siteCounter,
final MPResultType resultType,
final MPMasterKey.Version algorithmVersion) {
return new IncognitoSite( siteName, siteCounter, resultType, algorithmVersion );
}
};
}
@Override
protected IncognitoUser getSelectedUser() {
return new IncognitoUser( fullNameField.getText() ); return new IncognitoUser( fullNameField.getText() );
} }

View File

@ -20,19 +20,17 @@ package com.lyndir.masterpassword.gui.view;
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf; import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.collect.*; import com.google.common.collect.*;
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.masterpassword.MPMasterKey;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.model.ModelUser; import com.lyndir.masterpassword.model.*;
import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.model.MPUserFileManager;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.swing.*; import javax.swing.*;
import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener; import javax.swing.event.DocumentListener;
@ -47,7 +45,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( ModelAuthenticationPanel.class ); private static final Logger logger = Logger.get( ModelAuthenticationPanel.class );
private final JComboBox<ModelUser> userField; private final JComboBox<MPFileUser> userField;
private final JLabel masterPasswordLabel; private final JLabel masterPasswordLabel;
private final JPasswordField masterPasswordField; private final JPasswordField masterPasswordField;
@ -59,7 +57,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
avatarLabel.addMouseListener( new MouseAdapter() { avatarLabel.addMouseListener( new MouseAdapter() {
@Override @Override
public void mouseClicked(final MouseEvent e) { public void mouseClicked(final MouseEvent e) {
ModelUser selectedUser = getSelectedUser(); MPFileUser selectedUser = getSelectedUser();
if (selectedUser != null) { if (selectedUser != null) {
selectedUser.setAvatar( selectedUser.getAvatar() + 1 ); selectedUser.setAvatar( selectedUser.getAvatar() + 1 );
updateUser( false ); updateUser( false );
@ -104,10 +102,10 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
@Override @Override
protected void updateUser(boolean repack) { protected void updateUser(boolean repack) {
ModelUser selectedUser = getSelectedUser(); MPFileUser selectedUser = getSelectedUser();
if (selectedUser != null) { if (selectedUser != null) {
avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) ); avatarLabel.setIcon( Res.avatar( selectedUser.getAvatar() ) );
boolean showPasswordField = !selectedUser.keySaved(); boolean showPasswordField = !selectedUser.isMasterKeyAvailable(); // TODO: is this the same as keySaved()?
if (masterPasswordField.isVisible() != showPasswordField) { if (masterPasswordField.isVisible() != showPasswordField) {
masterPasswordLabel.setVisible( showPasswordField ); masterPasswordLabel.setVisible( showPasswordField );
masterPasswordField.setVisible( showPasswordField ); masterPasswordField.setVisible( showPasswordField );
@ -119,7 +117,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
} }
@Override @Override
protected ModelUser getSelectedUser() { protected MPFileUser getSelectedUser() {
int selectedIndex = userField.getSelectedIndex(); int selectedIndex = userField.getSelectedIndex();
if (selectedIndex < 0) if (selectedIndex < 0)
return null; return null;
@ -143,7 +141,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, // String fullName = JOptionPane.showInputDialog( ModelAuthenticationPanel.this, //
"Enter your full name, ensuring it is correctly spelled and capitalized:", "Enter your full name, ensuring it is correctly spelled and capitalized:",
"New User", JOptionPane.QUESTION_MESSAGE ); "New User", JOptionPane.QUESTION_MESSAGE );
MPUserFileManager.get().addUser( new MPUser( fullName ) ); MPFileUserManager.get().addUser( new MPFileUser( fullName ) );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) ); userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true ); updateUser( true );
} }
@ -155,7 +153,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
addActionListener( new ActionListener() { addActionListener( new ActionListener() {
@Override @Override
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
ModelUser deleteUser = getSelectedUser(); MPFileUser deleteUser = getSelectedUser();
if (deleteUser == null) if (deleteUser == null)
return; return;
@ -165,7 +163,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
"Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ) == JOptionPane.CANCEL_OPTION) "Delete User", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE ) == JOptionPane.CANCEL_OPTION)
return; return;
MPUserFileManager.get().deleteUser( deleteUser.getModel() ); MPFileUserManager.get().deleteUser( deleteUser );
userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) ); userField.setModel( new DefaultComboBoxModel<>( readConfigUsers() ) );
updateUser( true ); updateUser( true );
} }
@ -179,7 +177,7 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, // JOptionPane.showMessageDialog( ModelAuthenticationPanel.this, //
strf( "Reads users and sites from the directory at:\n%s", strf( "Reads users and sites from the directory at:\n%s",
MPUserFileManager.get().getPath().getAbsolutePath() ), // MPFileUserManager.get().getPath().getAbsolutePath() ), //
"Help", JOptionPane.INFORMATION_MESSAGE ); "Help", JOptionPane.INFORMATION_MESSAGE );
} }
} ); } );
@ -193,14 +191,19 @@ public class ModelAuthenticationPanel extends AuthenticationPanel implements Ite
masterPasswordField.setText( "" ); masterPasswordField.setText( "" );
} }
private static ModelUser[] readConfigUsers() {
return FluentIterable.from( MPUserFileManager.get().getUsers() ).transform( new Function<MPUser, ModelUser>() {
@Nullable
@Override @Override
public ModelUser apply(@Nullable final MPUser model) { public PasswordFrame<MPFileUser, MPFileSite> newPasswordFrame() {
return new ModelUser( Preconditions.checkNotNull( model ) ); return new PasswordFrame<MPFileUser, MPFileSite>(getSelectedUser()) {
@Override
protected MPFileSite createSite(final MPFileUser user, final String siteName, final UnsignedInteger siteCounter, final MPResultType resultType,
final MPMasterKey.Version algorithmVersion) {
return new MPFileSite( user, siteName, siteCounter, resultType, algorithmVersion );
} }
} ).toArray( ModelUser.class ); };
}
private static MPFileUser[] readConfigUsers() {
return MPFileUserManager.get().getUsers().toArray( new MPFileUser[0] );
} }
@Override @Override

View File

@ -26,11 +26,12 @@ import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.google.common.primitives.UnsignedInteger; import com.google.common.primitives.UnsignedInteger;
import com.google.common.util.concurrent.*; import com.google.common.util.concurrent.*;
import com.lyndir.lhunath.opal.system.util.ObjectUtils;
import com.lyndir.masterpassword.*; import com.lyndir.masterpassword.*;
import com.lyndir.masterpassword.gui.Res; import com.lyndir.masterpassword.gui.Res;
import com.lyndir.masterpassword.gui.model.*;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel; import com.lyndir.masterpassword.gui.util.UnsignedIntegerModel;
import com.lyndir.masterpassword.model.*;
import java.awt.*; import java.awt.*;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.awt.event.*; import java.awt.event.*;
@ -44,13 +45,13 @@ import javax.swing.event.*;
/** /**
* @author lhunath, 2014-06-08 * @author lhunath, 2014-06-08
*/ */
public class PasswordFrame extends JFrame implements DocumentListener { public abstract class PasswordFrame<U extends MPUser<S>, S extends MPSite> extends JFrame implements DocumentListener {
@SuppressWarnings("FieldCanBeLocal") @SuppressWarnings("FieldCanBeLocal")
private final Components.GradientPanel root; private final Components.GradientPanel root;
private final JTextField siteNameField; private final JTextField siteNameField;
private final JButton siteActionButton; private final JButton siteActionButton;
private final JComboBox<MasterKey.Version> siteVersionField; private final JComboBox<MPMasterKey.Version> siteVersionField;
private final JSpinner siteCounterField; private final JSpinner siteCounterField;
private final UnsignedIntegerModel siteCounterModel; private final UnsignedIntegerModel siteCounterModel;
private final JComboBox<MPResultType> resultTypeField; private final JComboBox<MPResultType> resultTypeField;
@ -59,13 +60,13 @@ public class PasswordFrame extends JFrame implements DocumentListener {
private final JCheckBox maskPasswordField; private final JCheckBox maskPasswordField;
private final char passwordEchoChar; private final char passwordEchoChar;
private final Font passwordEchoFont; private final Font passwordEchoFont;
private final User user; private final U user;
@Nullable @Nullable
private Site currentSite; private S currentSite;
private boolean updatingUI; private boolean updatingUI;
public PasswordFrame(final User user) { public PasswordFrame(final U user) {
super( "Master Password" ); super( "Master Password" );
this.user = user; this.user = user;
@ -122,7 +123,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
public void actionPerformed(final ActionEvent e) { public void actionPerformed(final ActionEvent e) {
if (currentSite == null) if (currentSite == null)
return; return;
if (currentSite instanceof ModelSite) if (currentSite instanceof MPFileSite)
PasswordFrame.this.user.deleteSite( currentSite ); PasswordFrame.this.user.deleteSite( currentSite );
else else
PasswordFrame.this.user.addSite( currentSite ); PasswordFrame.this.user.addSite( currentSite );
@ -140,7 +141,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, // JComponent siteSettings = Components.boxLayout( BoxLayout.LINE_AXIS, //
resultTypeField = Components.comboBox( types ), // resultTypeField = Components.comboBox( types ), //
Components.stud(), // Components.stud(), //
siteVersionField = Components.comboBox( MasterKey.Version.values() ), // siteVersionField = Components.comboBox( MPMasterKey.Version.values() ), //
Components.stud(), // Components.stud(), //
siteCounterField = Components.spinner( siteCounterModel ) ); siteCounterField = Components.spinner( siteCounterModel ) );
sitePanel.add( siteSettings ); sitePanel.add( siteSettings );
@ -155,7 +156,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) ); siteVersionField.setFont( Res.valueFont().deriveFont( 12f ) );
siteVersionField.setAlignmentX( RIGHT_ALIGNMENT ); siteVersionField.setAlignmentX( RIGHT_ALIGNMENT );
siteVersionField.setSelectedItem( MasterKey.Version.CURRENT ); siteVersionField.setSelectedItem( MPMasterKey.Version.CURRENT );
siteVersionField.addItemListener( new ItemListener() { siteVersionField.addItemListener( new ItemListener() {
@Override @Override
public void itemStateChanged(final ItemEvent e) { public void itemStateChanged(final ItemEvent e) {
@ -226,7 +227,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
final String siteNameQuery = siteNameField.getText(); final String siteNameQuery = siteNameField.getText();
if (updatingUI) if (updatingUI)
return Futures.immediateCancelledFuture(); return Futures.immediateCancelledFuture();
if ((siteNameQuery == null) || siteNameQuery.isEmpty() || !user.isKeyAvailable()) { if ((siteNameQuery == null) || siteNameQuery.isEmpty() || !user.isMasterKeyAvailable()) {
siteActionButton.setVisible( false ); siteActionButton.setVisible( false );
tipLabel.setText( null ); tipLabel.setText( null );
passwordField.setText( null ); passwordField.setText( null );
@ -234,19 +235,19 @@ public class PasswordFrame extends JFrame implements DocumentListener {
} }
MPResultType resultType = resultTypeField.getModel().getElementAt( resultTypeField.getSelectedIndex() ); MPResultType resultType = resultTypeField.getModel().getElementAt( resultTypeField.getSelectedIndex() );
MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() ); MPMasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
UnsignedInteger siteCounter = siteCounterModel.getNumber(); UnsignedInteger siteCounter = siteCounterModel.getNumber();
Iterable<Site> siteResults = user.findSitesByName( siteNameQuery ); Iterable<S> siteResults = user.findSites( siteNameQuery );
if (!allowNameCompletion) if (!allowNameCompletion)
siteResults = FluentIterable.from( siteResults ).filter( new Predicate<Site>() { siteResults = FluentIterable.from( siteResults ).filter( new Predicate<S>() {
@Override @Override
public boolean apply(@Nullable final Site siteResult) { public boolean apply(@Nullable final S siteResult) {
return (siteResult != null) && siteNameQuery.equals( siteResult.getSiteName() ); return (siteResult != null) && siteNameQuery.equals( siteResult.getSiteName() );
} }
} ); } );
final Site site = ifNotNullElse( Iterables.getFirst( siteResults, null ), final S site = ifNotNullElse( Iterables.getFirst( siteResults, null ),
new IncognitoSite( siteNameQuery, siteCounter, resultType, siteVersion ) ); createSite( user, siteNameQuery, siteCounter, resultType, siteVersion ) );
if ((currentSite != null) && currentSite.getSiteName().equals( site.getSiteName() )) { if ((currentSite != null) && currentSite.getSiteName().equals( site.getSiteName() )) {
site.setResultType( resultType ); site.setResultType( resultType );
site.setAlgorithmVersion( siteVersion ); site.setAlgorithmVersion( siteVersion );
@ -257,8 +258,9 @@ public class PasswordFrame extends JFrame implements DocumentListener {
@Override @Override
public String call() public String call()
throws Exception { throws Exception {
return user.getKey() return user.getMasterKey()
.siteResult( site.getSiteName(), site.getSiteCounter(), MPKeyPurpose.Authentication, null, site.getResultType(), null, site.getAlgorithmVersion() ); .siteResult( site.getSiteName(), site.getSiteCounter(), MPKeyPurpose.Authentication, null, site.getResultType(),
null, site.getAlgorithmVersion() );
} }
} ); } );
Futures.addCallback( passwordFuture, new FutureCallback<String>() { Futures.addCallback( passwordFuture, new FutureCallback<String>() {
@ -269,8 +271,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
public void run() { public void run() {
updatingUI = true; updatingUI = true;
currentSite = site; currentSite = site;
siteActionButton.setVisible( user instanceof ModelUser ); siteActionButton.setVisible( user instanceof MPFileUser );
if (currentSite instanceof ModelSite) if (currentSite instanceof MPFileSite)
siteActionButton.setText( "Delete Site" ); siteActionButton.setText( "Delete Site" );
else else
siteActionButton.setText( "Add Site" ); siteActionButton.setText( "Add Site" );
@ -296,6 +298,9 @@ public class PasswordFrame extends JFrame implements DocumentListener {
return passwordFuture; return passwordFuture;
} }
protected abstract S createSite(U user, String siteName, UnsignedInteger siteCounter, MPResultType resultType,
MPMasterKey.Version algorithmVersion);
@Override @Override
public void insertUpdate(final DocumentEvent e) { public void insertUpdate(final DocumentEvent e) {
updatePassword( true ); updatePassword( true );

View File

@ -22,9 +22,9 @@ import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import com.lyndir.masterpassword.MPIdenticon; import com.lyndir.masterpassword.MPIdenticon;
import com.lyndir.masterpassword.gui.*; import com.lyndir.masterpassword.gui.*;
import com.lyndir.masterpassword.gui.model.User; import com.lyndir.masterpassword.model.MPUser;
import com.lyndir.masterpassword.gui.util.Components; import com.lyndir.masterpassword.gui.util.Components;
import com.lyndir.masterpassword.model.IncorrectMasterPasswordException; import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -36,6 +36,7 @@ import javax.swing.*;
/** /**
* @author lhunath, 2014-06-08 * @author lhunath, 2014-06-08
*/ */
@SuppressWarnings({ "MagicNumber", "serial" })
public class UnlockFrame extends JFrame { public class UnlockFrame extends JFrame {
private final SignInCallback signInCallback; private final SignInCallback signInCallback;
@ -43,10 +44,10 @@ public class UnlockFrame extends JFrame {
private final JLabel identiconLabel; private final JLabel identiconLabel;
private final JButton signInButton; private final JButton signInButton;
private final JPanel authenticationContainer; private final JPanel authenticationContainer;
private AuthenticationPanel authenticationPanel; private AuthenticationPanel<?> authenticationPanel;
private Future<?> identiconFuture; private Future<?> identiconFuture;
private boolean incognito; private boolean incognito;
private User user; private MPUser<?> user;
public UnlockFrame(final SignInCallback signInCallback) { public UnlockFrame(final SignInCallback signInCallback) {
super( "Unlock Master Password" ); super( "Unlock Master Password" );
@ -155,7 +156,7 @@ public class UnlockFrame extends JFrame {
} ); } );
} }
void updateUser(@Nullable final User user) { void updateUser(@Nullable final MPUser<?> user) {
this.user = user; this.user = user;
checkSignIn(); checkSignIn();
} }
@ -213,12 +214,12 @@ public class UnlockFrame extends JFrame {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
signInCallback.signedIn( user ); signInCallback.signedIn( authenticationPanel.newPasswordFrame() );
dispose(); dispose();
} }
} ); } );
} }
catch (final IncorrectMasterPasswordException e) { catch (final MPIncorrectMasterPasswordException e) {
SwingUtilities.invokeLater( new Runnable() { SwingUtilities.invokeLater( new Runnable() {
@Override @Override
public void run() { public void run() {
@ -237,6 +238,6 @@ public class UnlockFrame extends JFrame {
public interface SignInCallback { public interface SignInCallback {
void signedIn(User user); void signedIn(PasswordFrame<?, ?> passwordFrame);
} }
} }