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
|
||||
String getName();
|
||||
|
||||
void setName(String name);
|
||||
|
||||
// - Algorithm
|
||||
|
||||
@Nonnull
|
||||
|
@ -19,6 +19,8 @@
|
||||
package com.lyndir.masterpassword.model;
|
||||
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicSite;
|
||||
import com.lyndir.masterpassword.model.impl.MPBasicUser;
|
||||
import java.util.Collection;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
@ -93,4 +95,15 @@ public interface MPUser<S extends MPSite<?>> extends Comparable<MPUser<?>> {
|
||||
|
||||
@Nonnull
|
||||
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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setName(final String name) {
|
||||
this.name = name;
|
||||
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPAlgorithm getAlgorithm() {
|
||||
|
@ -27,6 +27,7 @@ import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
@ -36,7 +37,8 @@ import javax.annotation.Nullable;
|
||||
*/
|
||||
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 final String fullName;
|
||||
@ -44,7 +46,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
||||
@Nullable
|
||||
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) {
|
||||
this( 0, fullName, algorithm );
|
||||
@ -128,6 +130,9 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
||||
throw new MPIncorrectMasterPasswordException( this );
|
||||
|
||||
this.masterKey = masterKey;
|
||||
|
||||
for (final Listener listener : listeners)
|
||||
listener.onUserAuthenticated( this );
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -147,14 +152,14 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
||||
|
||||
@Override
|
||||
public void addSite(final S site) {
|
||||
sites.add( site );
|
||||
sites.put( site.getName(), site );
|
||||
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteSite(final S site) {
|
||||
sites.remove( site );
|
||||
sites.values().remove( site );
|
||||
|
||||
setChanged();
|
||||
}
|
||||
@ -162,7 +167,7 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
||||
@Nonnull
|
||||
@Override
|
||||
public Collection<S> getSites() {
|
||||
return Collections.unmodifiableCollection( sites );
|
||||
return Collections.unmodifiableCollection( sites.values() );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@ -176,6 +181,24 @@ public abstract class MPBasicUser<S extends MPBasicSite<?>> extends Changeable i
|
||||
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
|
||||
public int hashCode() {
|
||||
return Objects.hashCode( getFullName() );
|
||||
|
@ -21,7 +21,8 @@ package com.lyndir.masterpassword.model.impl;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import com.lyndir.masterpassword.model.MPUser;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
import org.joda.time.ReadableInstant;
|
||||
@ -35,32 +36,31 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
|
||||
@Nullable
|
||||
private byte[] keyID;
|
||||
private File path;
|
||||
private MPMarshalFormat format;
|
||||
private MPMarshaller.ContentMode contentMode;
|
||||
|
||||
private MPResultType defaultType;
|
||||
private ReadableInstant lastUsed;
|
||||
|
||||
@Nullable
|
||||
private MPJSONFile json;
|
||||
|
||||
public MPFileUser(final String fullName) {
|
||||
this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm() );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm) {
|
||||
this( fullName, keyID, algorithm, 0, algorithm.mpw_default_result_type(), new Instant(),
|
||||
MPMarshalFormat.DEFAULT, MPMarshaller.ContentMode.PROTECTED );
|
||||
this( fullName, keyID, algorithm, 0, null, new Instant(),
|
||||
MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, MPFileUserManager.get().getPath() );
|
||||
}
|
||||
|
||||
public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm,
|
||||
final int avatar, final MPResultType defaultType, final ReadableInstant lastUsed,
|
||||
final MPMarshalFormat format, final MPMarshaller.ContentMode contentMode) {
|
||||
final int avatar, @Nullable final MPResultType defaultType, final ReadableInstant lastUsed,
|
||||
final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File path) {
|
||||
super( avatar, fullName, algorithm );
|
||||
|
||||
this.keyID = (keyID == null)? null: keyID.clone();
|
||||
this.defaultType = defaultType;
|
||||
this.keyID = (keyID != null)? keyID.clone(): null;
|
||||
this.defaultType = (defaultType != null)? defaultType: algorithm.mpw_default_result_type();
|
||||
this.lastUsed = lastUsed;
|
||||
this.path = path;
|
||||
this.format = format;
|
||||
this.contentMode = contentMode;
|
||||
}
|
||||
@ -131,15 +131,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
setChanged();
|
||||
}
|
||||
|
||||
public void setJSON(final MPJSONFile json) {
|
||||
this.json = json;
|
||||
|
||||
setChanged();
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public MPJSONFile getJSON() {
|
||||
return (json == null)? json = new MPJSONFile(): json;
|
||||
public File getFile() {
|
||||
return new File( path, getFullName() + getFormat().fileSuffix() );
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -147,6 +140,13 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
super.authenticate( masterKey );
|
||||
|
||||
try {
|
||||
getFormat().unmarshaller().readSites( this );
|
||||
}
|
||||
catch (final IOException | MPMarshalException e) {
|
||||
logger.err( e, "While reading sites on authentication." );
|
||||
}
|
||||
|
||||
if (keyID == null) {
|
||||
keyID = masterKey.getKeyID( getAlgorithm() );
|
||||
|
||||
@ -156,19 +156,17 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
|
||||
|
||||
@Override
|
||||
protected void onChanged() {
|
||||
super.onChanged();
|
||||
|
||||
try {
|
||||
save();
|
||||
getFormat().marshaller().marshall( this );
|
||||
}
|
||||
catch (final MPKeyUnavailableException | MPAlgorithmException e) {
|
||||
logger.wrn( e, "Couldn't save change." );
|
||||
catch (final MPKeyUnavailableException e) {
|
||||
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()
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
MPFileUserManager.get().save( this, getMasterKey() );
|
||||
super.onChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,16 +20,13 @@ package com.lyndir.masterpassword.model.impl;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.CharSink;
|
||||
import com.google.common.collect.ImmutableSortedSet;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
import java.io.*;
|
||||
import com.lyndir.masterpassword.model.MPConstants;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
/**
|
||||
@ -38,7 +35,7 @@ import javax.annotation.Nonnull;
|
||||
* @author lhunath, 14-12-07
|
||||
*/
|
||||
@SuppressWarnings("CallToSystemGetenv")
|
||||
public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
||||
public class MPFileUserManager {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPFileUserManager.class );
|
||||
@ -46,13 +43,17 @@ public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
||||
|
||||
static {
|
||||
String rcDir = System.getenv( MPConstants.env_rcDir );
|
||||
|
||||
if (rcDir != null)
|
||||
instance = create( new File( rcDir ) );
|
||||
else
|
||||
instance = create( new File( ifNotNullElseNullable( System.getProperty( "user.home" ), System.getenv( "HOME" ) ), ".mpw.d" ) );
|
||||
else {
|
||||
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() {
|
||||
return instance;
|
||||
@ -63,86 +64,53 @@ public class MPFileUserManager extends MPUserManager<MPFileUser> {
|
||||
}
|
||||
|
||||
protected MPFileUserManager(final File path) {
|
||||
|
||||
super( unmarshallUsers( path ) );
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
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();
|
||||
public void reload() {
|
||||
userByName.clear();
|
||||
|
||||
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 userFile : listUserFiles( userFilesDirectory ))
|
||||
for (final File file : pathFiles)
|
||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||
if (userFile.getName().endsWith( format.fileSuffix() ))
|
||||
if (file.getName().endsWith( format.fileSuffix() ))
|
||||
try {
|
||||
MPFileUser user = format.unmarshaller().unmarshall( userFile, null );
|
||||
MPFileUser previousUser = users.put( user.getFullName(), user );
|
||||
MPFileUser user = format.unmarshaller().readUser( file );
|
||||
MPFileUser previousUser = userByName.put( user.getFullName(), user );
|
||||
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) {
|
||||
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) {
|
||||
return ImmutableList.copyOf( ifNotNullElse( userFilesDirectory.listFiles( (dir, name) -> {
|
||||
for (final MPMarshalFormat format : MPMarshalFormat.values())
|
||||
if (name.endsWith( format.fileSuffix() ))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
} ), new File[0] ) );
|
||||
public MPFileUser add(final String fullName) {
|
||||
MPFileUser user = new MPFileUser( fullName );
|
||||
userByName.put( user.getFullName(), user );
|
||||
return user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteUser(final MPFileUser user) {
|
||||
super.deleteUser( user );
|
||||
|
||||
public void delete(final MPFileUser user) {
|
||||
// Remove deleted users.
|
||||
File userFile = getUserFile( user, user.getFormat() );
|
||||
File userFile = user.getFile();
|
||||
if (userFile.exists() && !userFile.delete())
|
||||
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() {
|
||||
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.StringUtils.*;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.CharSink;
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import com.lyndir.masterpassword.model.MPConstants;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.*;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
@ -36,10 +38,9 @@ public class MPFlatMarshaller implements MPMarshaller {
|
||||
|
||||
private static final int FORMAT = 1;
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String marshall(final MPFileUser user)
|
||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||
public void marshall(final MPFileUser user)
|
||||
throws IOException, MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||
StringBuilder content = new StringBuilder();
|
||||
content.append( "# Master Password site export\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;
|
||||
|
||||
import com.google.common.base.*;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.io.CharStreams;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||
@ -31,7 +31,6 @@ import java.io.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import org.joda.time.Instant;
|
||||
|
||||
|
||||
@ -49,114 +48,146 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
public MPFileUser readUser(@Nonnull final File file)
|
||||
throws IOException, MPMarshalException {
|
||||
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
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final String content, @Nullable final char[] masterPassword)
|
||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
MPFileUser user = null;
|
||||
byte[] keyID = null;
|
||||
String fullName = null;
|
||||
int mpVersion = 0, importFormat = 0, avatar = 0;
|
||||
boolean clearContent = false, headerStarted = false;
|
||||
MPResultType defaultType = null;
|
||||
|
||||
//noinspection HardcodedLineSeparator
|
||||
for (final String line : Splitter.on( CharMatcher.anyOf( "\r\n" ) ).omitEmptyStrings().split( content ))
|
||||
// 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();
|
||||
//noinspection HardcodedLineSeparator
|
||||
for (final String line : CharStreams.readLines( reader ))
|
||||
// Header delimitor.
|
||||
if (line.startsWith( "##" )) {
|
||||
if (!headerStarted)
|
||||
// Starts the header.
|
||||
headerStarted = true;
|
||||
else if ((fullName != null) && (keyID != null))
|
||||
// Ends the header.
|
||||
return new MPFileUser( fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(),
|
||||
avatar, defaultType, new Instant( 0 ),
|
||||
clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
|
||||
MPMarshalFormat.Flat, file.getParentFile() );
|
||||
}
|
||||
|
||||
// Comment.
|
||||
else if (line.startsWith( "#" )) {
|
||||
if (headerStarted && (user == null)) {
|
||||
// In header.
|
||||
Matcher headerMatcher = headerFormat.matcher( line );
|
||||
if (headerMatcher.matches()) {
|
||||
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
||||
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
||||
fullName = value;
|
||||
else if ("Key ID".equalsIgnoreCase( name ))
|
||||
keyID = CodeUtils.decodeHex( value );
|
||||
else if ("Algorithm".equalsIgnoreCase( name ))
|
||||
mpVersion = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Format".equalsIgnoreCase( name ))
|
||||
importFormat = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Avatar".equalsIgnoreCase( name ))
|
||||
avatar = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Passwords".equalsIgnoreCase( name ))
|
||||
clearContent = "visible".equalsIgnoreCase( value );
|
||||
else if ("Default Type".equalsIgnoreCase( name ))
|
||||
defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
|
||||
else if (line.startsWith( "#" )) {
|
||||
if (headerStarted) {
|
||||
// In header.
|
||||
Matcher headerMatcher = headerFormat.matcher( line );
|
||||
if (headerMatcher.matches()) {
|
||||
String name = headerMatcher.group( 1 ), value = headerMatcher.group( 2 );
|
||||
if ("Full Name".equalsIgnoreCase( name ) || "User Name".equalsIgnoreCase( name ))
|
||||
fullName = value;
|
||||
else if ("Key ID".equalsIgnoreCase( name ))
|
||||
keyID = CodeUtils.decodeHex( value );
|
||||
else if ("Algorithm".equalsIgnoreCase( name ))
|
||||
mpVersion = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Avatar".equalsIgnoreCase( name ))
|
||||
avatar = ConversionUtils.toIntegerNN( value );
|
||||
else if ("Passwords".equalsIgnoreCase( name ))
|
||||
clearContent = "visible".equalsIgnoreCase( 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." );
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ import com.lyndir.masterpassword.*;
|
||||
import com.lyndir.masterpassword.model.MPConstants;
|
||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
|
||||
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
|
||||
import java.io.File;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
@ -59,19 +60,20 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||
}
|
||||
|
||||
public MPJSONFile write(final MPFileUser modelUser)
|
||||
throws MPKeyUnavailableException, MPAlgorithmException {
|
||||
public MPJSONFile() {
|
||||
}
|
||||
|
||||
public MPJSONFile(final MPFileUser modelUser)
|
||||
throws MPAlgorithmException, MPKeyUnavailableException {
|
||||
|
||||
// Section: "export"
|
||||
if (export == null)
|
||||
export = new Export();
|
||||
export = new Export();
|
||||
export.format = 1;
|
||||
export.redacted = modelUser.getContentMode().isRedacted();
|
||||
export.date = MPConstants.dateTimeFormatter.print( new Instant() );
|
||||
|
||||
// Section: "user"
|
||||
if (user == null)
|
||||
user = new User();
|
||||
user = new User();
|
||||
user.avatar = modelUser.getAvatar();
|
||||
user.full_name = modelUser.getFullName();
|
||||
user.last_used = MPConstants.dateTimeFormatter.print( modelUser.getLastUsed() );
|
||||
@ -84,6 +86,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
sites = new LinkedHashMap<>();
|
||||
for (final MPFileSite modelSite : modelUser.getSites()) {
|
||||
String content = null, loginContent = null;
|
||||
|
||||
if (!export.redacted) {
|
||||
// Clear Text
|
||||
content = modelSite.getResult();
|
||||
@ -111,8 +114,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
site.uses = modelSite.getUses();
|
||||
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())
|
||||
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();
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public MPFileUser read(@Nullable final char[] masterPassword)
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
public MPFileUser readUser(final File file) {
|
||||
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.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
|
||||
(user.last_used != null)? MPConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
||||
model.ignoreChanges();
|
||||
model.setJSON( this );
|
||||
if (masterPassword != null)
|
||||
model.authenticate( masterPassword );
|
||||
export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
|
||||
MPMarshalFormat.JSON, file.getParentFile()
|
||||
);
|
||||
}
|
||||
|
||||
public void readSites(final MPFileUser user)
|
||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
user.ignoreChanges();
|
||||
|
||||
for (final Map.Entry<String, Site> siteEntry : sites.entrySet()) {
|
||||
String siteName = siteEntry.getKey();
|
||||
Site fileSite = siteEntry.getValue();
|
||||
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,
|
||||
fileSite.login_type, export.redacted? fileSite.login_name: null,
|
||||
(fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses,
|
||||
@ -168,18 +170,17 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
fileSite.login_name );
|
||||
}
|
||||
|
||||
model.addSite( site );
|
||||
user.addSite( site );
|
||||
}
|
||||
model.endChanges();
|
||||
|
||||
return model;
|
||||
user.endChanges();
|
||||
}
|
||||
|
||||
// -- Data
|
||||
|
||||
Export export;
|
||||
User user;
|
||||
Map<String, Site> sites;
|
||||
Export export = new Export();
|
||||
User user = new User();
|
||||
Map<String, Site> sites = new LinkedHashMap<>();
|
||||
|
||||
|
||||
public static class Export extends MPJSONAnyObject {
|
||||
@ -210,7 +211,7 @@ public class MPJSONFile extends MPJSONAnyObject {
|
||||
@Nullable
|
||||
MPResultType type;
|
||||
long counter;
|
||||
MPAlgorithm.Version algorithm;
|
||||
MPAlgorithm.Version algorithm = MPAlgorithm.Version.CURRENT;
|
||||
@Nullable
|
||||
String password;
|
||||
@Nullable
|
||||
|
@ -23,6 +23,7 @@ import static com.lyndir.masterpassword.model.impl.MPJSONFile.*;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.lyndir.masterpassword.MPAlgorithmException;
|
||||
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||
import java.io.IOException;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
|
||||
@ -33,11 +34,11 @@ public class MPJSONMarshaller implements MPMarshaller {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public String marshall(final MPFileUser user)
|
||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||
public void marshall(final MPFileUser user)
|
||||
throws IOException, MPKeyUnavailableException, MPMarshalException, MPAlgorithmException {
|
||||
|
||||
try {
|
||||
return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString( user.getJSON().write( user ) );
|
||||
objectMapper.writerWithDefaultPrettyPrinter().writeValue( user.getFile(), new MPJSONFile( user ) );
|
||||
}
|
||||
catch (final JsonProcessingException 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.IOException;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
@ -38,27 +37,11 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
@Override
|
||||
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException {
|
||||
public MPFileUser readUser(@Nonnull final File file)
|
||||
throws IOException, MPMarshalException {
|
||||
|
||||
try {
|
||||
return objectMapper.readValue( file, MPJSONFile.class ).read( masterPassword );
|
||||
}
|
||||
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 );
|
||||
return objectMapper.readValue( file, MPJSONFile.class ).readUser( file );
|
||||
}
|
||||
catch (final JsonParseException e) {
|
||||
throw new MPMarshalException( "Couldn't parse JSON.", e );
|
||||
@ -66,8 +49,20 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
|
||||
catch (final JsonMappingException 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.MPKeyUnavailableException;
|
||||
import javax.annotation.Nonnull;
|
||||
import java.io.IOException;
|
||||
|
||||
|
||||
/**
|
||||
@ -29,9 +29,8 @@ import javax.annotation.Nonnull;
|
||||
@FunctionalInterface
|
||||
public interface MPMarshaller {
|
||||
|
||||
@Nonnull
|
||||
String marshall(MPFileUser user)
|
||||
throws MPKeyUnavailableException, MPMarshalException, MPAlgorithmException;
|
||||
void marshall(MPFileUser user)
|
||||
throws IOException, MPKeyUnavailableException, MPMarshalException, MPAlgorithmException;
|
||||
|
||||
enum ContentMode {
|
||||
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.IOException;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
@ -33,10 +32,9 @@ import javax.annotation.Nullable;
|
||||
public interface MPUnmarshaller {
|
||||
|
||||
@Nonnull
|
||||
MPFileUser unmarshall(@Nonnull File file, @Nullable char[] masterPassword)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||
MPFileUser readUser(File file)
|
||||
throws IOException, MPMarshalException;
|
||||
|
||||
@Nonnull
|
||||
MPFileUser unmarshall(@Nonnull String content, @Nullable char[] masterPassword)
|
||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||
void readSites(MPFileUser user)
|
||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException, MPAlgorithmException;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user