Refactor model, improved isolation & access unauthenticated file metadata.
This commit is contained in:
parent
a16bc9a318
commit
80b5fcd785
@ -35,8 +35,6 @@ public interface MPSite<Q extends MPQuestion> extends Comparable<MPSite<?>> {
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
void setName(String name);
|
|
||||||
|
|
||||||
// - Algorithm
|
// - Algorithm
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -19,6 +19,8 @@
|
|||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
|
import com.lyndir.masterpassword.model.impl.MPBasicSite;
|
||||||
|
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -93,4 +95,15 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
Collection<S> findSites(String query);
|
Collection<S> findSites(String query);
|
||||||
|
|
||||||
|
boolean addListener(Listener listener);
|
||||||
|
|
||||||
|
boolean removeListener(Listener listener);
|
||||||
|
|
||||||
|
interface Listener {
|
||||||
|
|
||||||
|
void onUserUpdated(MPUser<?> user);
|
||||||
|
|
||||||
|
void onUserAuthenticated(MPUser<?> user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.model;
|
|
||||||
|
|
||||||
import com.google.common.collect.*;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 14-12-05
|
|
||||||
*/
|
|
||||||
public abstract class MPUserManager<U extends MPUser<?>> {
|
|
||||||
|
|
||||||
private final Map<String, U> usersByName = Maps.newHashMap();
|
|
||||||
|
|
||||||
protected MPUserManager(final Iterable<U> users) {
|
|
||||||
for (final U user : users)
|
|
||||||
usersByName.put( user.getFullName(), user );
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<U> getUsers() {
|
|
||||||
return ImmutableSortedSet.copyOf( usersByName.values() );
|
|
||||||
}
|
|
||||||
|
|
||||||
public U getUserNamed(final String fullName) {
|
|
||||||
return usersByName.get( fullName );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addUser(final U user) {
|
|
||||||
usersByName.put( user.getFullName(), user );
|
|
||||||
}
|
|
||||||
|
|
||||||
public void deleteUser(final U user) {
|
|
||||||
usersByName.remove( user.getFullName() );
|
|
||||||
}
|
|
||||||
}
|
|
@ -63,13 +63,6 @@ public abstract class MPBasicSite<Q extends MPQuestion> extends Changeable imple
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setName(final String name) {
|
|
||||||
this.name = name;
|
|
||||||
|
|
||||||
setChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPAlgorithm getAlgorithm() {
|
public MPAlgorithm getAlgorithm() {
|
||||||
|
@ -27,6 +27,7 @@ import com.lyndir.masterpassword.*;
|
|||||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -36,7 +37,8 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable implements MPUser<S> {
|
public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable implements MPUser<S> {
|
||||||
|
|
||||||
protected final Logger logger = Logger.get( getClass() );
|
protected final Logger logger = Logger.get( getClass() );
|
||||||
|
private final Set<Listener> listeners = new CopyOnWriteArraySet<>();
|
||||||
|
|
||||||
private int avatar;
|
private int avatar;
|
||||||
private final String fullName;
|
private final String fullName;
|
||||||
@ -44,7 +46,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
|||||||
@Nullable
|
@Nullable
|
||||||
protected MPMasterKey masterKey;
|
protected MPMasterKey masterKey;
|
||||||
|
|
||||||
private final Collection<S> sites = new LinkedHashSet<>();
|
private final Map<String, S> sites = new LinkedHashMap<>();
|
||||||
|
|
||||||
protected MPBasicUser(final String fullName, final MPAlgorithm algorithm) {
|
protected MPBasicUser(final String fullName, final MPAlgorithm algorithm) {
|
||||||
this( 0, fullName, algorithm );
|
this( 0, fullName, algorithm );
|
||||||
@ -128,6 +130,9 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
|||||||
throw new MPIncorrectMasterPasswordException( this );
|
throw new MPIncorrectMasterPasswordException( this );
|
||||||
|
|
||||||
this.masterKey = masterKey;
|
this.masterKey = masterKey;
|
||||||
|
|
||||||
|
for (final Listener listener : listeners)
|
||||||
|
listener.onUserAuthenticated( this );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,14 +152,14 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addSite(final S site) {
|
public void addSite(final S site) {
|
||||||
sites.add( site );
|
sites.put( site.getName(), site );
|
||||||
|
|
||||||
setChanged();
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteSite(final S site) {
|
public void deleteSite(final S site) {
|
||||||
sites.remove( site );
|
sites.values().remove( site );
|
||||||
|
|
||||||
setChanged();
|
setChanged();
|
||||||
}
|
}
|
||||||
@ -162,7 +167,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
|||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public Collection<S> getSites() {
|
public Collection<S> getSites() {
|
||||||
return Collections.unmodifiableCollection( sites );
|
return Collections.unmodifiableCollection( sites.values() );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -176,6 +181,24 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
|||||||
return results.build();
|
return results.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean addListener(final Listener listener) {
|
||||||
|
return listeners.add( listener );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean removeListener(final Listener listener) {
|
||||||
|
return listeners.remove( listener );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onChanged() {
|
||||||
|
super.onChanged();
|
||||||
|
|
||||||
|
for (final Listener listener : listeners)
|
||||||
|
listener.onUserUpdated( this );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode( getFullName() );
|
return Objects.hashCode( getFullName() );
|
||||||
|
@ -21,7 +21,8 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||||
import com.lyndir.masterpassword.model.MPUser;
|
import com.lyndir.masterpassword.model.MPUser;
|
||||||
import javax.annotation.Nonnull;
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.joda.time.Instant;
|
import org.joda.time.Instant;
|
||||||
import org.joda.time.ReadableInstant;
|
import org.joda.time.ReadableInstant;
|
||||||
@ -35,32 +36,31 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private byte[] keyID;
|
private byte[] keyID;
|
||||||
|
private File path;
|
||||||
private MPMarshalFormat format;
|
private MPMarshalFormat format;
|
||||||
private MPMarshaller.ContentMode contentMode;
|
private MPMarshaller.ContentMode contentMode;
|
||||||
|
|
||||||
private MPResultType defaultType;
|
private MPResultType defaultType;
|
||||||
private ReadableInstant lastUsed;
|
private ReadableInstant lastUsed;
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private MPJSONFile json;
|
|
||||||
|
|
||||||
public MPFileUser(final String fullName) {
|
public MPFileUser(final String fullName) {
|
||||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm) {
|
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm) {
|
||||||
this( fullName, keyID, algorithm, 0, algorithm.mpw_default_result_type(), new Instant(),
|
this( fullName, keyID, algorithm, 0, null, new Instant(),
|
||||||
MPMarshalFormat.DEFAULT, MPMarshaller.ContentMode.PROTECTED );
|
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, MPFileUserManager.get().getPath() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
|
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
|
||||||
final int avatar, final MPResultType defaultType, final ReadableInstant lastUsed,
|
final int avatar, @Nullable final MPResultType defaultType, final ReadableInstant lastUsed,
|
||||||
final MPMarshalFormat format, final MPMarshaller.ContentMode contentMode) {
|
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File path) {
|
||||||
super( avatar, fullName, algorithm );
|
super( avatar, fullName, algorithm );
|
||||||
|
|
||||||
this.keyID = (keyID == null)? null: keyID.clone();
|
this.keyID = (keyID != null)? keyID.clone(): null;
|
||||||
this.defaultType = defaultType;
|
this.defaultType = (defaultType != null)? defaultType: algorithm.mpw_default_result_type();
|
||||||
this.lastUsed = lastUsed;
|
this.lastUsed = lastUsed;
|
||||||
|
this.path = path;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.contentMode = contentMode;
|
this.contentMode = contentMode;
|
||||||
}
|
}
|
||||||
@ -131,15 +131,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
setChanged();
|
setChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setJSON(final MPJSONFile json) {
|
public File getFile() {
|
||||||
this.json = json;
|
return new File( path, getFullName() + getFormat().fileSuffix() );
|
||||||
|
|
||||||
setChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
public MPJSONFile getJSON() {
|
|
||||||
return (json == null)? json = new MPJSONFile(): json;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -147,6 +140,13 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||||
super.authenticate( masterKey );
|
super.authenticate( masterKey );
|
||||||
|
|
||||||
|
try {
|
||||||
|
getFormat().unmarshaller().readSites( this );
|
||||||
|
}
|
||||||
|
catch (final IOException | MPMarshalException e) {
|
||||||
|
logger.err( e, "While reading sites on authentication." );
|
||||||
|
}
|
||||||
|
|
||||||
if (keyID == null) {
|
if (keyID == null) {
|
||||||
keyID = masterKey.getKeyID( getAlgorithm() );
|
keyID = masterKey.getKeyID( getAlgorithm() );
|
||||||
|
|
||||||
@ -156,19 +156,17 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onChanged() {
|
protected void onChanged() {
|
||||||
super.onChanged();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
save();
|
getFormat().marshaller().marshall( this );
|
||||||
}
|
}
|
||||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
catch (final MPKeyUnavailableException e) {
|
||||||
logger.wrn( e, "Couldn't save change." );
|
logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
|
||||||
|
}
|
||||||
|
catch (final IOException | MPMarshalException | MPAlgorithmException e) {
|
||||||
|
logger.err( e, "Unable to write out changes for user: %s", this );
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void save()
|
super.onChanged();
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
|
||||||
MPFileUserManager.get().save( this, getMasterKey() );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,16 +20,13 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.collect.ImmutableSortedSet;
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
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.*;
|
import com.lyndir.masterpassword.model.MPConstants;
|
||||||
import com.lyndir.masterpassword.model.*;
|
import java.io.File;
|
||||||
import java.io.*;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nonnull;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,7 +35,7 @@ import javax.annotation.Nonnull;
|
|||||||
* @author lhunath, 14-12-07
|
* @author lhunath, 14-12-07
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("CallToSystemGetenv")
|
@SuppressWarnings("CallToSystemGetenv")
|
||||||
public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
public class MPFileUserManager {
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
||||||
@ -46,13 +43,17 @@ public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
|||||||
|
|
||||||
static {
|
static {
|
||||||
String rcDir = System.getenv( MPConstants.env_rcDir );
|
String rcDir = System.getenv( MPConstants.env_rcDir );
|
||||||
|
|
||||||
if (rcDir != null)
|
if (rcDir != null)
|
||||||
instance = create( new File( rcDir ) );
|
instance = create( new File( rcDir ) );
|
||||||
else
|
else {
|
||||||
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
|
String home = ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) );
|
||||||
|
instance = create( new File( home, ".mpw.d" ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final File path;
|
private final Map<String, MPFileUser> userByName = new HashMap<>();
|
||||||
|
private final File path;
|
||||||
|
|
||||||
public static MPFileUserManager get() {
|
public static MPFileUserManager get() {
|
||||||
return instance;
|
return instance;
|
||||||
@ -63,86 +64,53 @@ public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected MPFileUserManager(final File path) {
|
protected MPFileUserManager(final File path) {
|
||||||
|
|
||||||
super( unmarshallUsers( path ) );
|
|
||||||
this.path = path;
|
this.path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Iterable<MPFileUser> unmarshallUsers(final File userFilesDirectory) {
|
public void reload() {
|
||||||
if (!userFilesDirectory.mkdirs() && !userFilesDirectory.isDirectory()) {
|
userByName.clear();
|
||||||
logger.err( "Couldn't create directory for user files: %s", userFilesDirectory );
|
|
||||||
return ImmutableList.of();
|
File[] pathFiles;
|
||||||
|
if ((!path.exists() && !path.mkdirs()) || ((pathFiles = path.listFiles()) == null)) {
|
||||||
|
logger.err( "Couldn't create directory for user files: %s", path );
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, MPFileUser> users = new HashMap<>();
|
for (final File file : pathFiles)
|
||||||
for (final File userFile : listUserFiles( userFilesDirectory ))
|
|
||||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||||
if (userFile.getName().endsWith( format.fileSuffix() ))
|
if (file.getName().endsWith( format.fileSuffix() ))
|
||||||
try {
|
try {
|
||||||
MPFileUser user = format.unmarshaller().unmarshall( userFile, null );
|
MPFileUser user = format.unmarshaller().readUser( file );
|
||||||
MPFileUser previousUser = users.put( user.getFullName(), user );
|
MPFileUser previousUser = userByName.put( user.getFullName(), user );
|
||||||
if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
|
if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
|
||||||
users.put( previousUser.getFullName(), previousUser );
|
userByName.put( previousUser.getFullName(), previousUser );
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
catch (final IOException | MPMarshalException e) {
|
catch (final IOException | MPMarshalException e) {
|
||||||
logger.err( e, "Couldn't read user from: %s", userFile );
|
logger.err( e, "Couldn't read user from: %s", file );
|
||||||
}
|
}
|
||||||
catch (final MPKeyUnavailableException | MPIncorrectMasterPasswordException | MPAlgorithmException e) {
|
|
||||||
logger.err( e, "Couldn't authenticate user for: %s", userFile );
|
|
||||||
}
|
|
||||||
|
|
||||||
return users.values();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ImmutableList<File> listUserFiles(final File userFilesDirectory) {
|
public MPFileUser add(final String fullName) {
|
||||||
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( (dir, name) -> {
|
MPFileUser user = new MPFileUser( fullName );
|
||||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
userByName.put( user.getFullName(), user );
|
||||||
if (name.endsWith( format.fileSuffix() ))
|
return user;
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
} ), new File[0] ) );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public void delete(final MPFileUser user) {
|
||||||
public void deleteUser(final MPFileUser user) {
|
|
||||||
super.deleteUser( user );
|
|
||||||
|
|
||||||
// Remove deleted users.
|
// Remove deleted users.
|
||||||
File userFile = getUserFile( user, user.getFormat() );
|
File userFile = user.getFile();
|
||||||
if (userFile.exists() && !userFile.delete())
|
if (userFile.exists() && !userFile.delete())
|
||||||
logger.err( "Couldn't delete file: %s", userFile );
|
logger.err( "Couldn't delete file: %s", userFile );
|
||||||
|
else
|
||||||
|
userByName.values().remove( user );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write the current user state to disk.
|
|
||||||
*/
|
|
||||||
public void save(final MPFileUser user, final MPMasterKey masterKey)
|
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
|
||||||
try {
|
|
||||||
MPMarshalFormat format = user.getFormat();
|
|
||||||
new CharSink() {
|
|
||||||
@Override
|
|
||||||
public Writer openStream()
|
|
||||||
throws IOException {
|
|
||||||
return new OutputStreamWriter( new FileOutputStream( getUserFile( user, format ) ), Charsets.UTF_8 );
|
|
||||||
}
|
|
||||||
}.write( format.marshaller().marshall( user ) );
|
|
||||||
}
|
|
||||||
catch (final MPMarshalException | IOException e) {
|
|
||||||
logger.err( e, "Unable to save sites for user: %s", user );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
private File getUserFile(final MPUser<?> user, final MPMarshalFormat format) {
|
|
||||||
return new File( path, user.getFullName() + format.fileSuffix() );
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return The location on the file system where the user models are stored.
|
|
||||||
*/
|
|
||||||
public File getPath() {
|
public File getPath() {
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ImmutableSortedSet<MPFileUser> getFiles() {
|
||||||
|
return ImmutableSortedSet.copyOf( userByName.values() );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,10 +21,12 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.CharSink;
|
||||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||||
import com.lyndir.masterpassword.model.MPConstants;
|
import com.lyndir.masterpassword.model.MPConstants;
|
||||||
import javax.annotation.Nonnull;
|
import java.io.*;
|
||||||
import org.joda.time.Instant;
|
import org.joda.time.Instant;
|
||||||
|
|
||||||
|
|
||||||
@ -36,10 +38,9 @@ public class MPFlatMarshaller implements MPMarshaller {
|
|||||||
|
|
||||||
private static final int FORMAT = 1;
|
private static final int FORMAT = 1;
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
@Override
|
||||||
public String marshall(final MPFileUser user)
|
public void marshall(final MPFileUser user)
|
||||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
throws IOException, MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||||
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( user.getContentMode().description() ).append( '\n' );
|
content.append( "# " ).append( user.getContentMode().description() ).append( '\n' );
|
||||||
@ -80,6 +81,12 @@ public class MPFlatMarshaller implements MPMarshaller {
|
|||||||
) );
|
) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return content.toString();
|
new CharSink() {
|
||||||
|
@Override
|
||||||
|
public Writer openStream()
|
||||||
|
throws IOException {
|
||||||
|
return new OutputStreamWriter( new FileOutputStream( user.getFile() ), Charsets.UTF_8 );
|
||||||
|
}
|
||||||
|
}.write( content.toString() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model.impl;
|
package com.lyndir.masterpassword.model.impl;
|
||||||
|
|
||||||
import com.google.common.base.*;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
import com.google.common.primitives.UnsignedInteger;
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
@ -31,7 +31,6 @@ import java.io.*;
|
|||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
import org.joda.time.Instant;
|
import org.joda.time.Instant;
|
||||||
|
|
||||||
|
|
||||||
@ -49,114 +48,146 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
public MPFileUser readUser(@Nonnull final File file)
|
||||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
throws IOException, MPMarshalException {
|
||||||
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 ), masterPassword );
|
byte[] keyID = null;
|
||||||
}
|
String fullName = null;
|
||||||
}
|
int mpVersion = 0, avatar = 0;
|
||||||
|
boolean clearContent = false, headerStarted = false;
|
||||||
|
MPResultType defaultType = null;
|
||||||
|
|
||||||
@Nonnull
|
//noinspection HardcodedLineSeparator
|
||||||
@Override
|
for (final String line : CharStreams.readLines( reader ))
|
||||||
public MPFileUser unmarshall(@Nonnull final String content, @Nullable final char[] masterPassword)
|
// Header delimitor.
|
||||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
if (line.startsWith( "##" )) {
|
||||||
MPFileUser user = null;
|
if (!headerStarted)
|
||||||
byte[] keyID = null;
|
// Starts the header.
|
||||||
String fullName = null;
|
headerStarted = true;
|
||||||
int mpVersion = 0, importFormat = 0, avatar = 0;
|
else if ((fullName != null) && (keyID != null))
|
||||||
boolean clearContent = false, headerStarted = false;
|
// Ends the header.
|
||||||
MPResultType defaultType = null;
|
return new MPFileUser( fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(),
|
||||||
|
avatar, defaultType, new Instant( 0 ),
|
||||||
//noinspection HardcodedLineSeparator
|
clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
|
||||||
for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
|
MPMarshalFormat.Flat, file.getParentFile() );
|
||||||
// Header delimitor.
|
|
||||||
if (line.startsWith( "##" ))
|
|
||||||
if (!headerStarted)
|
|
||||||
// Starts the header.
|
|
||||||
headerStarted = true;
|
|
||||||
else {
|
|
||||||
// Ends the header.
|
|
||||||
user = new MPFileUser( fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(),
|
|
||||||
avatar, defaultType, new Instant( 0 ), MPMarshalFormat.Flat,
|
|
||||||
clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED );
|
|
||||||
user.ignoreChanges();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comment.
|
// Comment.
|
||||||
else if (line.startsWith( "#" )) {
|
else if (line.startsWith( "#" )) {
|
||||||
if (headerStarted && (user == null)) {
|
if (headerStarted) {
|
||||||
// In header.
|
// In header.
|
||||||
Matcher headerMatcher = headerFormat.matcher( line );
|
Matcher headerMatcher = headerFormat.matcher( line );
|
||||||
if (headerMatcher.matches()) {
|
if (headerMatcher.matches()) {
|
||||||
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
||||||
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
||||||
fullName = value;
|
fullName = value;
|
||||||
else if ("Key ID".equalsIgnoreCase( name ))
|
else if ("Key ID".equalsIgnoreCase( name ))
|
||||||
keyID = CodeUtils.decodeHex( value );
|
keyID = CodeUtils.decodeHex( value );
|
||||||
else if ("Algorithm".equalsIgnoreCase( name ))
|
else if ("Algorithm".equalsIgnoreCase( name ))
|
||||||
mpVersion = ConversionUtils.toIntegerNN( value );
|
mpVersion = ConversionUtils.toIntegerNN( value );
|
||||||
else if ("Format".equalsIgnoreCase( name ))
|
else if ("Avatar".equalsIgnoreCase( name ))
|
||||||
importFormat = ConversionUtils.toIntegerNN( value );
|
avatar = ConversionUtils.toIntegerNN( value );
|
||||||
else if ("Avatar".equalsIgnoreCase( name ))
|
else if ("Passwords".equalsIgnoreCase( name ))
|
||||||
avatar = ConversionUtils.toIntegerNN( value );
|
clearContent = "visible".equalsIgnoreCase( value );
|
||||||
else if ("Passwords".equalsIgnoreCase( name ))
|
else if ("Default Type".equalsIgnoreCase( name ))
|
||||||
clearContent = "visible".equalsIgnoreCase( value );
|
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
|
||||||
else if ("Default Type".equalsIgnoreCase( name ))
|
}
|
||||||
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// No comment.
|
|
||||||
else if (user != null) {
|
|
||||||
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
|
|
||||||
if (!siteMatcher.matches()) {
|
|
||||||
logger.wrn( "Couldn't parse line: %s, skipping.", line );
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
MPFileSite site;
|
|
||||||
switch (importFormat) {
|
|
||||||
case 0:
|
|
||||||
site = new MPFileSite( user, //
|
|
||||||
siteMatcher.group( 5 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
|
||||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
|
||||||
user.getAlgorithm().mpw_default_counter(),
|
|
||||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
|
||||||
clearContent? null: siteMatcher.group( 6 ),
|
|
||||||
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
|
||||||
MPConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
|
||||||
if (clearContent)
|
|
||||||
site.setSitePassword( site.getResultType(), siteMatcher.group( 6 ) );
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
site = new MPFileSite( user, //
|
|
||||||
siteMatcher.group( 7 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
|
||||||
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
|
||||||
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
|
||||||
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
|
||||||
clearContent? null: siteMatcher.group( 8 ),
|
|
||||||
MPResultType.GeneratedName, clearContent? null: siteMatcher.group( 6 ), null,
|
|
||||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
|
||||||
MPConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
|
||||||
if (clearContent) {
|
|
||||||
site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) );
|
|
||||||
site.setLoginName( MPResultType.StoredPersonal, siteMatcher.group( 6 ) );
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new MPMarshalException( "Unexpected format: " + importFormat );
|
|
||||||
}
|
|
||||||
|
|
||||||
user.addSite( site );
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
throw new MPMarshalException( "No full header found in import file." );
|
throw new MPMarshalException( "No full header found in import file." );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readSites(final MPFileUser user)
|
||||||
|
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
user.ignoreChanges();
|
||||||
|
|
||||||
|
try (Reader reader = new InputStreamReader( new FileInputStream( user.getFile() ), Charsets.UTF_8 )) {
|
||||||
|
byte[] keyID = null;
|
||||||
|
String fullName = null;
|
||||||
|
int mpVersion = 0, importFormat = 0, avatar = 0;
|
||||||
|
boolean clearContent = false, headerStarted = false, headerEnded = false;
|
||||||
|
MPResultType defaultType = null;
|
||||||
|
|
||||||
|
//noinspection HardcodedLineSeparator
|
||||||
|
for (final String line : CharStreams.readLines( reader ))
|
||||||
|
// Header delimitor.
|
||||||
|
if (line.startsWith( "##" )) {
|
||||||
|
if (!headerStarted)
|
||||||
|
// Starts the header.
|
||||||
|
headerStarted = true;
|
||||||
|
else
|
||||||
|
// Ends the header.
|
||||||
|
headerEnded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment.
|
||||||
|
else if (line.startsWith( "#" )) {
|
||||||
|
if (headerStarted && !headerEnded) {
|
||||||
|
// In header.
|
||||||
|
Matcher headerMatcher = headerFormat.matcher( line );
|
||||||
|
if (headerMatcher.matches()) {
|
||||||
|
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
||||||
|
if ("Format".equalsIgnoreCase( name ))
|
||||||
|
importFormat = ConversionUtils.toIntegerNN( value );
|
||||||
|
else if ("Passwords".equalsIgnoreCase( name ))
|
||||||
|
clearContent = "visible".equalsIgnoreCase( value );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No comment.
|
||||||
|
else if (headerEnded) {
|
||||||
|
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( line );
|
||||||
|
if (!siteMatcher.matches()) {
|
||||||
|
logger.wrn( "Couldn't parse line: %s, skipping.", line );
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MPFileSite site;
|
||||||
|
switch (importFormat) {
|
||||||
|
case 0:
|
||||||
|
site = new MPFileSite( user, //
|
||||||
|
siteMatcher.group( 5 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||||
|
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||||
|
user.getAlgorithm().mpw_default_counter(),
|
||||||
|
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||||
|
clearContent? null: siteMatcher.group( 6 ),
|
||||||
|
null, null, null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||||
|
MPConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||||
|
if (clearContent)
|
||||||
|
site.setSitePassword( site.getResultType(), siteMatcher.group( 6 ) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
site = new MPFileSite( user, //
|
||||||
|
siteMatcher.group( 7 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
|
||||||
|
colon.matcher( siteMatcher.group( 4 ) ).replaceAll( "" ) ) ).getAlgorithm(),
|
||||||
|
UnsignedInteger.valueOf( colon.matcher( siteMatcher.group( 5 ) ).replaceAll( "" ) ),
|
||||||
|
MPResultType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||||
|
clearContent? null: siteMatcher.group( 8 ),
|
||||||
|
MPResultType.GeneratedName, clearContent? null: siteMatcher.group( 6 ), null,
|
||||||
|
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
|
||||||
|
MPConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
|
||||||
|
if (clearContent) {
|
||||||
|
site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) );
|
||||||
|
site.setLoginName( MPResultType.StoredPersonal, siteMatcher.group( 6 ) );
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new MPMarshalException( "Unexpected format: " + importFormat );
|
||||||
|
}
|
||||||
|
|
||||||
|
user.addSite( site );
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
throw new MPMarshalException( "No full header found in import file." );
|
||||||
|
}
|
||||||
|
|
||||||
user.endChanges();
|
user.endChanges();
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import com.lyndir.masterpassword.*;
|
|||||||
import com.lyndir.masterpassword.model.MPConstants;
|
import com.lyndir.masterpassword.model.MPConstants;
|
||||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||||
|
import java.io.File;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -59,19 +60,20 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPJSONFile write(final MPFileUser modelUser)
|
public MPJSONFile() {
|
||||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
}
|
||||||
|
|
||||||
|
public MPJSONFile(final MPFileUser modelUser)
|
||||||
|
throws MPAlgorithmException, MPKeyUnavailableException {
|
||||||
|
|
||||||
// Section: "export"
|
// Section: "export"
|
||||||
if (export == null)
|
export = new Export();
|
||||||
export = new Export();
|
|
||||||
export.format = 1;
|
export.format = 1;
|
||||||
export.redacted = modelUser.getContentMode().isRedacted();
|
export.redacted = modelUser.getContentMode().isRedacted();
|
||||||
export.date = MPConstants.dateTimeFormatter.print( new Instant() );
|
export.date = MPConstants.dateTimeFormatter.print( new Instant() );
|
||||||
|
|
||||||
// Section: "user"
|
// Section: "user"
|
||||||
if (user == null)
|
user = new User();
|
||||||
user = new User();
|
|
||||||
user.avatar = modelUser.getAvatar();
|
user.avatar = modelUser.getAvatar();
|
||||||
user.full_name = modelUser.getFullName();
|
user.full_name = modelUser.getFullName();
|
||||||
user.last_used = MPConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
|
user.last_used = MPConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
|
||||||
@ -84,6 +86,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
sites = new LinkedHashMap<>();
|
sites = new LinkedHashMap<>();
|
||||||
for (final MPFileSite modelSite : modelUser.getSites()) {
|
for (final MPFileSite modelSite : modelUser.getSites()) {
|
||||||
String content = null, loginContent = null;
|
String content = null, loginContent = null;
|
||||||
|
|
||||||
if (!export.redacted) {
|
if (!export.redacted) {
|
||||||
// Clear Text
|
// Clear Text
|
||||||
content = modelSite.getResult();
|
content = modelSite.getResult();
|
||||||
@ -111,8 +114,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
site.uses = modelSite.getUses();
|
site.uses = modelSite.getUses();
|
||||||
site.last_used = MPConstants.dateTimeFormatter.print( modelSite.getLastUsed() );
|
site.last_used = MPConstants.dateTimeFormatter.print( modelSite.getLastUsed() );
|
||||||
|
|
||||||
if (site.questions == null)
|
site.questions = new LinkedHashMap<>();
|
||||||
site.questions = new LinkedHashMap<>();
|
|
||||||
for (final MPFileQuestion question : modelSite.getQuestions())
|
for (final MPFileQuestion question : modelSite.getQuestions())
|
||||||
site.questions.put( question.getKeyword(), new Site.Question() {
|
site.questions.put( question.getKeyword(), new Site.Question() {
|
||||||
{
|
{
|
||||||
@ -129,32 +131,32 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
|
|
||||||
if (site._ext_mpw == null)
|
site._ext_mpw = new Site.Ext();
|
||||||
site._ext_mpw = new Site.Ext();
|
|
||||||
site._ext_mpw.url = modelSite.getUrl();
|
site._ext_mpw.url = modelSite.getUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser read(@Nullable final char[] masterPassword)
|
public MPFileUser readUser(final File file) {
|
||||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
|
||||||
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT ).getAlgorithm();
|
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPAlgorithm.Version.CURRENT ).getAlgorithm();
|
||||||
MPFileUser model = new MPFileUser(
|
|
||||||
|
return new MPFileUser(
|
||||||
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
||||||
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
(user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
||||||
(user.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
(user.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||||
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
|
||||||
model.ignoreChanges();
|
MPMarshalFormat.JSON, file.getParentFile()
|
||||||
model.setJSON( this );
|
);
|
||||||
if (masterPassword != null)
|
}
|
||||||
model.authenticate( masterPassword );
|
|
||||||
|
public void readSites(final MPFileUser user)
|
||||||
|
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
user.ignoreChanges();
|
||||||
|
|
||||||
for (final Map.Entry<String, Site> siteEntry : sites.entrySet()) {
|
for (final Map.Entry<String, Site> siteEntry : sites.entrySet()) {
|
||||||
String siteName = siteEntry.getKey();
|
String siteName = siteEntry.getKey();
|
||||||
Site fileSite = siteEntry.getValue();
|
Site fileSite = siteEntry.getValue();
|
||||||
MPFileSite site = new MPFileSite(
|
MPFileSite site = new MPFileSite(
|
||||||
model, siteName, fileSite.algorithm.getAlgorithm(), UnsignedInteger.valueOf( fileSite.counter ), fileSite.type,
|
user, siteName, fileSite.algorithm.getAlgorithm(), UnsignedInteger.valueOf( fileSite.counter ), fileSite.type,
|
||||||
export.redacted? fileSite.password: null,
|
export.redacted? fileSite.password: null,
|
||||||
fileSite.login_type, export.redacted? fileSite.login_name: null,
|
fileSite.login_type, export.redacted? fileSite.login_name: null,
|
||||||
(fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses,
|
(fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses,
|
||||||
@ -168,18 +170,17 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
fileSite.login_name );
|
fileSite.login_name );
|
||||||
}
|
}
|
||||||
|
|
||||||
model.addSite( site );
|
user.addSite( site );
|
||||||
}
|
}
|
||||||
model.endChanges();
|
|
||||||
|
|
||||||
return model;
|
user.endChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Data
|
// -- Data
|
||||||
|
|
||||||
Export export;
|
Export export = new Export();
|
||||||
User user;
|
User user = new User();
|
||||||
Map<String, Site> sites;
|
Map<String, Site> sites = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
|
||||||
public static class Export extends MPJSONAnyObject {
|
public static class Export extends MPJSONAnyObject {
|
||||||
@ -210,7 +211,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
|||||||
@Nullable
|
@Nullable
|
||||||
MPResultType type;
|
MPResultType type;
|
||||||
long counter;
|
long counter;
|
||||||
MPAlgorithm.Version algorithm;
|
MPAlgorithm.Version algorithm = MPAlgorithm.Version.CURRENT;
|
||||||
@Nullable
|
@Nullable
|
||||||
String password;
|
String password;
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -23,6 +23,7 @@ import static com.lyndir.masterpassword.model.impl.MPJSONFile.*;
|
|||||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||||
|
import java.io.IOException;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
|
||||||
@ -33,11 +34,11 @@ public class MPJSONMarshaller implements MPMarshaller {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String marshall(final MPFileUser user)
|
public void marshall(final MPFileUser user)
|
||||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
throws IOException, MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString( user.getJSON().write( user ) );
|
objectMapper.writerWithDefaultPrettyPrinter().writeValue( user.getFile(), new MPJSONFile( user ) );
|
||||||
}
|
}
|
||||||
catch (final JsonProcessingException e) {
|
catch (final JsonProcessingException e) {
|
||||||
throw new MPMarshalException( "Couldn't compose JSON for: " + user, e );
|
throw new MPMarshalException( "Couldn't compose JSON for: " + user, e );
|
||||||
|
@ -28,7 +28,6 @@ import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,27 +37,11 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
public MPFileUser readUser(@Nonnull final File file)
|
||||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
throws IOException, MPMarshalException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return objectMapper.readValue( file, MPJSONFile.class ).read( masterPassword );
|
return objectMapper.readValue( file, MPJSONFile.class ).readUser( file );
|
||||||
}
|
|
||||||
catch (final JsonParseException e) {
|
|
||||||
throw new MPMarshalException( "Couldn't parse JSON in: " + file, e );
|
|
||||||
}
|
|
||||||
catch (final JsonMappingException e) {
|
|
||||||
throw new MPMarshalException( "Couldn't map JSON in: " + file, e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nonnull
|
|
||||||
@Override
|
|
||||||
public MPFileUser unmarshall(@Nonnull final String content, @Nullable final char[] masterPassword)
|
|
||||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
|
||||||
|
|
||||||
try {
|
|
||||||
return objectMapper.readValue( content, MPJSONFile.class ).read( masterPassword );
|
|
||||||
}
|
}
|
||||||
catch (final JsonParseException e) {
|
catch (final JsonParseException e) {
|
||||||
throw new MPMarshalException( "Couldn't parse JSON.", e );
|
throw new MPMarshalException( "Couldn't parse JSON.", e );
|
||||||
@ -66,8 +49,20 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
|
|||||||
catch (final JsonMappingException e) {
|
catch (final JsonMappingException e) {
|
||||||
throw new MPMarshalException( "Couldn't map JSON.", e );
|
throw new MPMarshalException( "Couldn't map JSON.", e );
|
||||||
}
|
}
|
||||||
catch (final IOException e) {
|
}
|
||||||
throw new MPMarshalException( "Couldn't read JSON.", e );
|
|
||||||
|
@Override
|
||||||
|
public void readSites(final MPFileUser user)
|
||||||
|
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||||
|
|
||||||
|
try {
|
||||||
|
objectMapper.readValue( user.getFile(), MPJSONFile.class ).readSites( user );
|
||||||
|
}
|
||||||
|
catch (final JsonParseException e) {
|
||||||
|
throw new MPMarshalException( "Couldn't parse JSON.", e );
|
||||||
|
}
|
||||||
|
catch (final JsonMappingException e) {
|
||||||
|
throw new MPMarshalException( "Couldn't map JSON.", e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ package com.lyndir.masterpassword.model.impl;
|
|||||||
|
|
||||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||||
import javax.annotation.Nonnull;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,9 +29,8 @@ import javax.annotation.Nonnull;
|
|||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface MPMarshaller {
|
public interface MPMarshaller {
|
||||||
|
|
||||||
@Nonnull
|
void marshall(MPFileUser user)
|
||||||
String marshall(MPFileUser user)
|
throws IOException, MPKeyUnavailableException, MPMarshalException, MPAlgorithmException;
|
||||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException;
|
|
||||||
|
|
||||||
enum ContentMode {
|
enum ContentMode {
|
||||||
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key.", true ),
|
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key.", true ),
|
||||||
|
@ -24,7 +24,6 @@ import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -33,10 +32,9 @@ import javax.annotation.Nullable;
|
|||||||
public interface MPUnmarshaller {
|
public interface MPUnmarshaller {
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
MPFileUser unmarshall(@Nonnull File file, @Nullable char[] masterPassword)
|
MPFileUser readUser(File file)
|
||||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
throws IOException, MPMarshalException;
|
||||||
|
|
||||||
@Nonnull
|
void readSites(MPFileUser user)
|
||||||
MPFileUser unmarshall(@Nonnull String content, @Nullable char[] masterPassword)
|
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user