Fix file import & sane format migration.
This commit is contained in:
		
							
								
								
									
										2
									
								
								platform-darwin/External/Pearl
									
									
									
									
										vendored
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								platform-darwin/External/Pearl
									
									
									
									
										vendored
									
									
								
							 Submodule platform-darwin/External/Pearl updated: bc737d41fa...3d04d775e0
									
								
							@@ -3,7 +3,7 @@ package com.lyndir.masterpassword.gui.view;
 | 
			
		||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.*;
 | 
			
		||||
import com.google.common.collect.*;
 | 
			
		||||
import com.google.common.collect.ImmutableList;
 | 
			
		||||
import com.google.common.primitives.UnsignedInteger;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
@@ -407,6 +407,9 @@ public class UserContentPanel extends JPanel implements MasterPassword.Listener,
 | 
			
		||||
            Res.job( () -> {
 | 
			
		||||
                try {
 | 
			
		||||
                    user.authenticate( masterPassword );
 | 
			
		||||
 | 
			
		||||
                    if (user instanceof MPFileUser)
 | 
			
		||||
                        ((MPFileUser) user).migrateTo( MPMarshalFormat.DEFAULT );
 | 
			
		||||
                }
 | 
			
		||||
                catch (final MPIncorrectMasterPasswordException e) {
 | 
			
		||||
                    logger.wrn( e, "During user authentication for: %s", user );
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,6 @@ import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPUser;
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import javax.annotation.Nonnull;
 | 
			
		||||
import javax.annotation.Nullable;
 | 
			
		||||
import org.joda.time.Instant;
 | 
			
		||||
@@ -41,7 +40,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    private byte[]                   keyID;
 | 
			
		||||
    private File                     path;
 | 
			
		||||
    private File                     file;
 | 
			
		||||
    private MPMarshalFormat          format;
 | 
			
		||||
    private MPMarshaller.ContentMode contentMode;
 | 
			
		||||
 | 
			
		||||
@@ -54,33 +53,37 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
    public static MPFileUser load(final File file)
 | 
			
		||||
            throws IOException, MPMarshalException {
 | 
			
		||||
        for (final MPMarshalFormat format : MPMarshalFormat.values())
 | 
			
		||||
            if (file.getName().endsWith( format.fileSuffix() ))
 | 
			
		||||
            if (format.matches(file))
 | 
			
		||||
                return format.unmarshaller().readUser( file );
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPFileUser(final String fullName, final File path) {
 | 
			
		||||
        this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), path );
 | 
			
		||||
    public MPFileUser(final String fullName, final File location) {
 | 
			
		||||
        this( fullName, null, MPAlgorithm.Version.CURRENT.getAlgorithm(), location );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File path) {
 | 
			
		||||
    public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final File location) {
 | 
			
		||||
        this( fullName, keyID, algorithm, 0, null, new Instant(), false,
 | 
			
		||||
              MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, path );
 | 
			
		||||
              MPMarshaller.ContentMode.PROTECTED, MPMarshalFormat.DEFAULT, location );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPFileUser(final String fullName, @Nullable final byte[] keyID, final MPAlgorithm algorithm, final int avatar,
 | 
			
		||||
                      @Nullable final MPResultType defaultType, final ReadableInstant lastUsed, final boolean hidePasswords,
 | 
			
		||||
                      final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File path) {
 | 
			
		||||
                      final MPMarshaller.ContentMode contentMode, final MPMarshalFormat format, final File location) {
 | 
			
		||||
        super( avatar, fullName, algorithm );
 | 
			
		||||
 | 
			
		||||
        this.keyID = (keyID != null)? keyID.clone(): null;
 | 
			
		||||
        this.defaultType = (defaultType != null)? defaultType: algorithm.mpw_default_result_type();
 | 
			
		||||
        this.lastUsed = lastUsed;
 | 
			
		||||
        this.hidePasswords = hidePasswords;
 | 
			
		||||
        this.path = path;
 | 
			
		||||
        this.format = format;
 | 
			
		||||
        this.contentMode = contentMode;
 | 
			
		||||
 | 
			
		||||
        if (location.isDirectory())
 | 
			
		||||
            this.file = new File( location, getFullName() + getFormat().fileSuffix() );
 | 
			
		||||
        else
 | 
			
		||||
            this.file = location;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
@@ -89,10 +92,6 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        return (keyID == null)? null: keyID.clone();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setPath(final File path) {
 | 
			
		||||
        this.path = path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void setAlgorithm(final MPAlgorithm algorithm) {
 | 
			
		||||
        if (!algorithm.equals( getAlgorithm() ) && (keyID != null)) {
 | 
			
		||||
@@ -117,20 +116,12 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        return format;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setFormat(final MPMarshalFormat format) {
 | 
			
		||||
        if (Objects.equals( this.format, format ))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        this.format = format;
 | 
			
		||||
        setChanged();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPMarshaller.ContentMode getContentMode() {
 | 
			
		||||
        return contentMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setContentMode(final MPMarshaller.ContentMode contentMode) {
 | 
			
		||||
        if (Objects.equals( this.contentMode, contentMode ))
 | 
			
		||||
        if (this.contentMode == contentMode)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        this.contentMode = contentMode;
 | 
			
		||||
@@ -143,7 +134,7 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void setDefaultType(final MPResultType defaultType) {
 | 
			
		||||
        if (Objects.equals( this.defaultType, defaultType ))
 | 
			
		||||
        if (this.defaultType == defaultType)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        this.defaultType = defaultType;
 | 
			
		||||
@@ -180,7 +171,47 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public File getFile() {
 | 
			
		||||
        return new File( path, getFullName() + getFormat().fileSuffix() );
 | 
			
		||||
        return file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void migrateTo(final MPMarshalFormat format) {
 | 
			
		||||
        if (this.format == format)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        migrateTo( file.getParentFile(), format );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void migrateTo(final File path) {
 | 
			
		||||
        migrateTo( path, format );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Move the file for this user to the given path using a standard user-derived filename (ie. {@code [full name].[format suffix]})
 | 
			
		||||
     *
 | 
			
		||||
     * The user's old file is either moved to the new or deleted.  If the user's file was already at the destination, it doesn't change.
 | 
			
		||||
     * If a file already exists at the destination, it is overwritten.
 | 
			
		||||
     */
 | 
			
		||||
    public void migrateTo(final File path, final MPMarshalFormat newFormat) {
 | 
			
		||||
        MPMarshalFormat oldFormat = format;
 | 
			
		||||
        File oldFile = file, newFile = new File( path, getFullName() + newFormat.fileSuffix() );
 | 
			
		||||
 | 
			
		||||
        // If the format hasn't changed, migrate by moving the file: the contents doesn't need to change.
 | 
			
		||||
        if ((oldFormat == newFormat) && !oldFile.equals( newFile ) && oldFile.exists())
 | 
			
		||||
            if (!oldFile.renameTo( newFile ))
 | 
			
		||||
                logger.wrn( "Couldn't move %s to %s for migration.", oldFile, newFile );
 | 
			
		||||
 | 
			
		||||
        this.format = newFormat;
 | 
			
		||||
        this.file = newFile;
 | 
			
		||||
 | 
			
		||||
        // If the format has changed, save the new format into the new file and delete the old file.  Revert if the user cannot be saved.
 | 
			
		||||
        if ((oldFormat != newFormat) && !oldFile.equals( newFile ))
 | 
			
		||||
            if (save()) {
 | 
			
		||||
                if (oldFile.exists() && !oldFile.delete())
 | 
			
		||||
                    logger.wrn( "Couldn't delete %s after migration.", oldFile );
 | 
			
		||||
            } else {
 | 
			
		||||
                this.format = oldFormat;
 | 
			
		||||
                this.file = oldFile;
 | 
			
		||||
            }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
@@ -201,10 +232,16 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void save() {
 | 
			
		||||
    /**
 | 
			
		||||
     * @return {@code false} if the user is not fully loaded (complete), authenticated, or an issue prevented the marshalling.
 | 
			
		||||
     */
 | 
			
		||||
    public boolean save() {
 | 
			
		||||
        if (!isComplete())
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            if (isComplete())
 | 
			
		||||
            getFormat().marshaller().marshall( this );
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        catch (final MPKeyUnavailableException e) {
 | 
			
		||||
            logger.wrn( e, "Cannot write out changes for unauthenticated user: %s.", this );
 | 
			
		||||
@@ -212,6 +249,8 @@ public class MPFileUser extends MPBasicUser<MPFileSite> {
 | 
			
		||||
        catch (final IOException | MPMarshalException | MPAlgorithmException e) {
 | 
			
		||||
            logger.err( e, "Unable to write out changes for user: %s", this );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -78,14 +78,13 @@ public class MPFileUserManager {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (final MPMarshalFormat format : MPMarshalFormat.values())
 | 
			
		||||
            for (final File file : pathFiles)
 | 
			
		||||
                if (format.matches( file ))
 | 
			
		||||
                    try {
 | 
			
		||||
                        MPFileUser user = MPFileUser.load( file );
 | 
			
		||||
                if (user != null) {
 | 
			
		||||
                    MPFileUser previousUser = userByName.put( user.getFullName(), user );
 | 
			
		||||
                    if ((previousUser != null) && (previousUser.getFormat().ordinal() > user.getFormat().ordinal()))
 | 
			
		||||
                        userByName.put( previousUser.getFullName(), previousUser );
 | 
			
		||||
                }
 | 
			
		||||
                        if (user != null)
 | 
			
		||||
                            add( user );
 | 
			
		||||
                    }
 | 
			
		||||
                    catch (final IOException | MPMarshalException e) {
 | 
			
		||||
                        logger.err( e, "Couldn't read user from: %s", file );
 | 
			
		||||
@@ -99,12 +98,19 @@ public class MPFileUserManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public MPFileUser add(final MPFileUser user) {
 | 
			
		||||
        user.setPath( getPath() );
 | 
			
		||||
        user.save();
 | 
			
		||||
        // We migrate in two steps to allow the first to complete even if the user is not in the right state to complete the latter.
 | 
			
		||||
        user.migrateTo( getPath() );
 | 
			
		||||
        user.migrateTo( MPMarshalFormat.DEFAULT );
 | 
			
		||||
 | 
			
		||||
        MPFileUser oldUser = userByName.put( user.getFullName(), user );
 | 
			
		||||
        if (oldUser != null)
 | 
			
		||||
        if (oldUser != null) {
 | 
			
		||||
            oldUser.invalidate();
 | 
			
		||||
 | 
			
		||||
            // Delete old user, it is replaced by the new one.
 | 
			
		||||
            if (!oldUser.getFile().equals( user.getFile() ) && oldUser.getFile().exists())
 | 
			
		||||
                if (!oldUser.getFile().delete())
 | 
			
		||||
                    logger.err( "Couldn't delete file: %s, after replacing with: %s", oldUser.getFile(), user.getFile() );
 | 
			
		||||
        }
 | 
			
		||||
        fireUpdated();
 | 
			
		||||
 | 
			
		||||
        return user;
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,8 @@ import com.lyndir.lhunath.opal.system.CodeUtils;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.logging.Logger;
 | 
			
		||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
 | 
			
		||||
import com.lyndir.masterpassword.*;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPModelConstants;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPIncorrectMasterPasswordException;
 | 
			
		||||
import com.lyndir.masterpassword.model.MPModelConstants;
 | 
			
		||||
import java.io.*;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
@@ -56,6 +56,7 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
            int          mpVersion    = 0, avatar = 0;
 | 
			
		||||
            boolean      clearContent = false, headerStarted = false;
 | 
			
		||||
            MPResultType defaultType  = null;
 | 
			
		||||
            Instant      date         = null;
 | 
			
		||||
 | 
			
		||||
            //noinspection HardcodedLineSeparator
 | 
			
		||||
            for (final String line : CharStreams.readLines( reader ))
 | 
			
		||||
@@ -66,10 +67,11 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
                        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 ), false,
 | 
			
		||||
                                               clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
 | 
			
		||||
                                               MPMarshalFormat.Flat, file.getParentFile() );
 | 
			
		||||
                        return new MPFileUser(
 | 
			
		||||
                                fullName, keyID, MPAlgorithm.Version.fromInt( mpVersion ).getAlgorithm(), avatar, defaultType,
 | 
			
		||||
                                date, false, clearContent? MPMarshaller.ContentMode.VISIBLE: MPMarshaller.ContentMode.PROTECTED,
 | 
			
		||||
                                MPMarshalFormat.Flat, file
 | 
			
		||||
                        );
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                // Comment.
 | 
			
		||||
@@ -91,6 +93,8 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
                                clearContent = "visible".equalsIgnoreCase( value );
 | 
			
		||||
                            else if ("Default Type".equalsIgnoreCase( name ))
 | 
			
		||||
                                defaultType = MPResultType.forType( ConversionUtils.toIntegerNN( value ) );
 | 
			
		||||
                            else if ("Date".equalsIgnoreCase( name ))
 | 
			
		||||
                                date = MPModelConstants.dateTimeFormatter.parseDateTime( value ).toInstant();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
@@ -150,8 +154,9 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
                        MPFileSite site;
 | 
			
		||||
                        switch (importFormat) {
 | 
			
		||||
                            case 0:
 | 
			
		||||
                                site = new MPFileSite( user, //
 | 
			
		||||
                                                       siteMatcher.group( 5 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
 | 
			
		||||
                                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 ) ) ),
 | 
			
		||||
@@ -163,14 +168,17 @@ public class MPFlatUnmarshaller implements MPUnmarshaller {
 | 
			
		||||
                                break;
 | 
			
		||||
 | 
			
		||||
                            case 1:
 | 
			
		||||
                                site = new MPFileSite( user, //
 | 
			
		||||
                                                       siteMatcher.group( 7 ), MPAlgorithm.Version.fromInt( ConversionUtils.toIntegerNN(
 | 
			
		||||
                                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( "" ) ),
 | 
			
		||||
                                        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 ) ),
 | 
			
		||||
                                        MPResultType.GeneratedName,
 | 
			
		||||
                                        clearContent? null: siteMatcher.group( 6 ),
 | 
			
		||||
                                        null, ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ),
 | 
			
		||||
                                        MPModelConstants.dateTimeFormatter.parseDateTime( siteMatcher.group( 1 ) ).toInstant() );
 | 
			
		||||
                                if (clearContent) {
 | 
			
		||||
                                    site.setSitePassword( site.getResultType(), siteMatcher.group( 8 ) );
 | 
			
		||||
 
 | 
			
		||||
@@ -144,7 +144,7 @@ public class MPJSONFile extends MPJSONAnyObject {
 | 
			
		||||
                (user.default_type != null)? user.default_type: algorithm.mpw_default_result_type(),
 | 
			
		||||
                (user.last_used != null)? MPModelConstants.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
 | 
			
		||||
                user.hide_passwords, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE,
 | 
			
		||||
                MPMarshalFormat.JSON, file.getParentFile()
 | 
			
		||||
                MPMarshalFormat.JSON, file
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,9 @@
 | 
			
		||||
 | 
			
		||||
package com.lyndir.masterpassword.model.impl;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author lhunath, 2017-09-20
 | 
			
		||||
 *
 | 
			
		||||
@@ -72,4 +75,8 @@ public enum MPMarshalFormat {
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("MethodReturnAlwaysConstant")
 | 
			
		||||
    public abstract String fileSuffix();
 | 
			
		||||
 | 
			
		||||
    public boolean matches(final File file) {
 | 
			
		||||
        return file.getName().endsWith( fileSuffix() );
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
 Submodule public/site updated: d8d510b6be...ebd2c741fd
									
								
							
		Reference in New Issue
	
	Block a user