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

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

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.StringUtils.strf;
import com.lyndir.masterpassword.MPConstant;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.*;
import org.joda.time.Instant;
@@ -34,7 +33,8 @@ public class MPFlatMarshaller implements MPMarshaller {
private static final int FORMAT = 1;
@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();
content.append( "# Master Password site export\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( "# Avatar: " ).append( user.getAvatar() ).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( "# Passwords: " ).append( contentMode.name() ).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( "# 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 password = site.getSiteContent();
if (!contentMode.isRedacted()) {

View File

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

View File

@@ -21,17 +21,17 @@ package com.lyndir.masterpassword.model;
/**
* @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() );
this.user = user;
}
public MPUser getUser() {
public MPFileUser getUser() {
return user;
}
}

View File

@@ -18,7 +18,7 @@
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 {
@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
return null;
}

View File

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

View File

@@ -18,7 +18,8 @@
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 {
String marshall(MPUser user, MasterKey masterKey, ContentMode contentMode);
String marshall(MPFileUser user, MPMasterKey masterKey, ContentMode contentMode)
throws MPInvalidatedException;
enum ContentMode {
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 java.util.Objects;
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;
private final MPUser user;
private String siteName;
@Nullable
private String siteContent;
private UnsignedInteger siteCounter;
private MPResultType resultType;
private MasterKey.Version algorithmVersion;
public abstract String getSiteName();
@Nullable
private String loginContent;
@Nullable
private MPResultType loginType;
public abstract void setSiteName(String siteName);
@Nullable
private String url;
private int uses;
private Instant lastUsed;
public abstract UnsignedInteger getSiteCounter();
public MPSite(final MPUser user, final String siteName) {
this( user, siteName, DEFAULT_COUNTER, MPResultType.DEFAULT );
public abstract void setSiteCounter(UnsignedInteger siteCounter);
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) {
this.user = user;
this.siteName = siteName;
this.siteCounter = siteCounter;
this.resultType = resultType;
this.algorithmVersion = MasterKey.Version.CURRENT;
this.lastUsed = new Instant();
}
public String loginFor(final MPMasterKey masterKey, final MPResultType loginType, @Nullable final String loginContent)
throws MPInvalidatedException {
protected MPSite(final MPUser user, final String siteName, @Nullable final String siteContent, final UnsignedInteger siteCounter,
final MPResultType resultType, final MasterKey.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 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;
return masterKey.siteResult(
getSiteName(), DEFAULT_COUNTER, MPKeyPurpose.Identification, null, loginType, loginContent, getAlgorithmVersion() );
}
@Override
@@ -202,6 +76,6 @@ public class MPSite {
@Override
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 {
private final MPSite site;
private final MPFileSite site;
public MPSiteResult(final MPSite site) {
public MPSiteResult(final MPFileSite site) {
this.site = site;
}
public MPSite getSite() {
public MPFileSite getSite() {
return site;
}

View File

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

View File

@@ -18,155 +18,69 @@
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.collect.Sets;
import com.google.common.base.Preconditions;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.masterpassword.MPResultType;
import com.lyndir.masterpassword.MasterKey;
import com.lyndir.masterpassword.MPInvalidatedException;
import com.lyndir.masterpassword.MPMasterKey;
import java.util.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import org.joda.time.*;
/**
* @author lhunath, 14-12-07
* @author lhunath, 2014-06-08
*/
public class MPUser implements Comparable<MPUser> {
private final String fullName;
private final Collection<MPSite> sites = Sets.newHashSet();
public abstract class MPUser<S extends MPSite> {
@Nullable
private byte[] keyID;
private MasterKey.Version algorithmVersion;
protected MPMasterKey key;
private int avatar;
private MPResultType defaultType;
private ReadableInstant lastUsed;
public abstract String getFullName();
public MPUser(final String fullName) {
this( fullName, null, MasterKey.Version.CURRENT );
public boolean isMasterKeyAvailable() {
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
@SuppressWarnings("MethodCanBeVariableArityMethod")
public MasterKey authenticate(final char[] masterPassword)
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 MPMasterKey getMasterKey() {
return Preconditions.checkNotNull( key, "User is not authenticated: " + getFullName() );
}
public String exportKeyID()
throws MPInvalidatedException {
return CodeUtils.encodeHex( getMasterKey().getKeyID( getAlgorithmVersion() ) );
}
public abstract MPMasterKey.Version getAlgorithmVersion();
public int getAvatar() {
return avatar;
return 0;
}
public void setAvatar(final int avatar) {
this.avatar = avatar;
}
public abstract void addSite(S site);
public MPResultType getDefaultType() {
return defaultType;
}
public abstract void deleteSite(S site);
public void setDefaultType(final MPResultType defaultType) {
this.defaultType = defaultType;
}
public abstract Collection<S> findSites(String query);
public ReadableInstant getLastUsed() {
return lastUsed;
}
@Nonnull
public abstract MPMasterKey authenticate(char[] masterPassword)
throws MPIncorrectMasterPasswordException;
public void updateLastUsed() {
lastUsed = new Instant();
}
public Iterable<MPSite> getSites() {
return sites;
@Override
public int hashCode() {
return Objects.hashCode( getFullName() );
}
@Override
public boolean equals(final Object obj) {
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( fullName, ((MPUser) obj).fullName ));
}
@Override
public int hashCode() {
return Objects.hashCode( fullName );
return (this == obj) || ((obj instanceof MPUser) && Objects.equals( getFullName(), ((MPUser<?>) obj).getFullName() ));
}
@Override
public String toString() {
return strf( "{MPUser: %s}", fullName );
}
@Override
public int compareTo(final MPUser o) {
int comparison = lastUsed.compareTo( o.lastUsed );
if (comparison == 0)
comparison = fullName.compareTo( o.fullName );
return comparison;
return strf( "{%s: %s}", getClass().getSimpleName(), getFullName() );
}
}

View File

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