Transition to Jackson so we can retain unrecognized properties in the source JSON.
This commit is contained in:
parent
f0d523fb35
commit
38a357cb28
@ -11,6 +11,7 @@ dependencies {
|
|||||||
compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p11'
|
compile group: 'com.lyndir.lhunath.opal', name: 'opal-crypto', version: '1.6-p11'
|
||||||
|
|
||||||
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0'
|
compile group: 'com.lambdaworks', name: 'scrypt', version: '1.4.0'
|
||||||
|
compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.5'
|
||||||
compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
compile group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||||
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
|
compile group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.1'
|
||||||
}
|
}
|
||||||
|
@ -18,28 +18,8 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
|
||||||
import com.google.common.io.CharStreams;
|
|
||||||
import com.lyndir.masterpassword.model.*;
|
|
||||||
import java.io.*;
|
|
||||||
import org.testng.Assert;
|
|
||||||
import org.testng.annotations.Test;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2018-04-27
|
* @author lhunath, 2017-09-21
|
||||||
*/
|
*/
|
||||||
public class MPModelTest {
|
public class MPKeyUnavailableException extends Exception {
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testMasterKey()
|
|
||||||
throws Exception {
|
|
||||||
File file = new File( "/Users/lhunath/.mpw.d/Maarten Billemont.mpsites.json" );
|
|
||||||
String orig = CharStreams.toString( new InputStreamReader( new FileInputStream( file ), Charsets.UTF_8 ) );
|
|
||||||
System.out.println(orig);
|
|
||||||
MPFileUser user = new MPJSONUnmarshaller().unmarshall( file, null );
|
|
||||||
String result = new MPJSONMarshaller().marshall( user );
|
|
||||||
System.out.println(result);
|
|
||||||
Assert.assertEquals( result, orig, "Marshalled sites do not match original sites." );
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
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;
|
||||||
@ -232,11 +234,13 @@ public class MPMasterKey {
|
|||||||
return algorithm;
|
return algorithm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonCreator
|
||||||
public static Version fromInt(final int algorithmVersion) {
|
public static Version fromInt(final int algorithmVersion) {
|
||||||
|
|
||||||
return values()[algorithmVersion];
|
return values()[algorithmVersion];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
public int toInt() {
|
public int toInt() {
|
||||||
|
|
||||||
return ordinal();
|
return ordinal();
|
||||||
|
@ -18,6 +18,8 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
@ -179,6 +181,7 @@ public enum MPResultType {
|
|||||||
return typeFeatures.contains( feature );
|
return typeFeatures.contains( feature );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
public int getType() {
|
public int getType() {
|
||||||
int mask = typeIndex | typeClass.getMask();
|
int mask = typeIndex | typeClass.getMask();
|
||||||
for (final MPSiteFeature typeFeature : typeFeatures)
|
for (final MPSiteFeature typeFeature : typeFeatures)
|
||||||
@ -226,6 +229,7 @@ public enum MPResultType {
|
|||||||
*
|
*
|
||||||
* @return The type registered with the given type.
|
* @return The type registered with the given type.
|
||||||
*/
|
*/
|
||||||
|
@JsonCreator
|
||||||
public static MPResultType forType(final int type) {
|
public static MPResultType forType(final int type) {
|
||||||
|
|
||||||
for (final MPResultType resultType : values())
|
for (final MPResultType resultType : values())
|
||||||
|
@ -7,13 +7,14 @@ description = 'Master Password Site Model'
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compile project( ':masterpassword-algorithm' )
|
compile project( ':masterpassword-algorithm' )
|
||||||
|
compile 'joda-time:joda-time:2.4'
|
||||||
compile group: 'joda-time', name: 'joda-time', version: '2.4'
|
compile 'com.fasterxml.jackson.core:jackson-core:2.9.5'
|
||||||
compile group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
|
compile 'com.fasterxml.jackson.core:jackson-annotations:2.9.5'
|
||||||
compileOnly group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
compile 'com.fasterxml.jackson.core:jackson-databind:2.9.5'
|
||||||
|
//compile group: 'com.google.code.gson', name: 'gson', version: '2.8.2'
|
||||||
|
compileOnly 'com.google.auto.value:auto-value:1.2'
|
||||||
apt group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
apt group: 'com.google.auto.value', name: 'auto-value', version: '1.2'
|
||||||
|
testCompile 'org.testng:testng:6.8.5'
|
||||||
testCompile group: 'org.testng', name: 'testng', version: '6.8.5'
|
testCompile 'ch.qos.logback:logback-classic:1.1.2'
|
||||||
testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
|
|
||||||
}
|
}
|
||||||
test.useTestNG()
|
test.useTestNG()
|
||||||
|
@ -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.gson.*;
|
|
||||||
import java.lang.reflect.Type;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author lhunath, 2018-04-27
|
|
||||||
*/
|
|
||||||
public class EnumOrdinalAdapter implements JsonSerializer<Enum<?>>, JsonDeserializer<Enum<?>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Enum<?> deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
|
|
||||||
throws JsonParseException {
|
|
||||||
Enum<?>[] enumConstants = ((Class<Enum<?>>) typeOfT).getEnumConstants();
|
|
||||||
if (enumConstants == null)
|
|
||||||
throw new JsonParseException( "Not an enum: " + typeOfT );
|
|
||||||
|
|
||||||
try {
|
|
||||||
int ordinal = json.getAsInt();
|
|
||||||
if ((ordinal < 0) || (ordinal >= enumConstants.length))
|
|
||||||
throw new JsonParseException( "No ordinal " + ordinal + " in enum: " + typeOfT );
|
|
||||||
|
|
||||||
return enumConstants[ordinal];
|
|
||||||
} catch (final ClassCastException | IllegalStateException e) {
|
|
||||||
throw new JsonParseException( "Not an ordinal value: " + json, e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JsonElement serialize(final Enum<?> src, final Type typeOfSrc, final JsonSerializationContext context) {
|
|
||||||
return new JsonPrimitive( src.ordinal() );
|
|
||||||
}
|
|
||||||
}
|
|
@ -51,6 +51,9 @@ public class MPFileUser extends MPUser<MPFileSite> implements Comparable<MPFileU
|
|||||||
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, MPMasterKey.Version.CURRENT.getAlgorithm() );
|
this( fullName, null, MPMasterKey.Version.CURRENT.getAlgorithm() );
|
||||||
}
|
}
|
||||||
@ -157,6 +160,15 @@ public class MPFileUser extends MPUser<MPFileSite> implements Comparable<MPFileU
|
|||||||
return results.build();
|
return results.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setJSON(final MPJSONFile json) {
|
||||||
|
this.json = json;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public MPJSONFile getJSON() {
|
||||||
|
return (json == null)? json = new MPJSONFile(): json;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Performs an authentication attempt against the keyID for this user.
|
* Performs an authentication attempt against the keyID for this user.
|
||||||
*
|
*
|
||||||
|
@ -18,29 +18,21 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import com.fasterxml.jackson.annotation.JsonAnyGetter;
|
||||||
import com.lyndir.masterpassword.MPResultType;
|
import com.fasterxml.jackson.annotation.JsonAnySetter;
|
||||||
import java.lang.reflect.Type;
|
import java.util.*;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2018-04-27
|
* @author lhunath, 2018-05-14
|
||||||
*/
|
*/
|
||||||
public class MPResultTypeAdapter implements JsonSerializer<MPResultType>, JsonDeserializer<MPResultType> {
|
class MPJSONAnyObject {
|
||||||
|
|
||||||
@Override
|
@JsonAnySetter
|
||||||
public MPResultType deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context)
|
final Map<String, Object> any = new LinkedHashMap<>();
|
||||||
throws JsonParseException {
|
|
||||||
try {
|
|
||||||
return MPResultType.forType( json.getAsInt() );
|
|
||||||
}
|
|
||||||
catch (final ClassCastException | IllegalStateException e) {
|
|
||||||
throw new JsonParseException( "Not an ordinal value: " + json, e );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@JsonAnyGetter
|
||||||
public JsonElement serialize(final MPResultType src, final Type typeOfSrc, final JsonSerializationContext context) {
|
public Map<String, Object> getAny() {
|
||||||
return new JsonPrimitive( src.getType() );
|
return Collections.unmodifiableMap( any );
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,11 +18,19 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.*;
|
||||||
|
import com.fasterxml.jackson.core.*;
|
||||||
|
import com.fasterxml.jackson.databind.*;
|
||||||
|
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||||
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;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import java.util.LinkedHashMap;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.*;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import org.joda.time.Instant;
|
import org.joda.time.Instant;
|
||||||
|
|
||||||
@ -30,61 +38,72 @@ import org.joda.time.Instant;
|
|||||||
/**
|
/**
|
||||||
* @author lhunath, 2018-04-27
|
* @author lhunath, 2018-04-27
|
||||||
*/
|
*/
|
||||||
public class MPJSONFile {
|
public class MPJSONFile extends MPJSONAnyObject {
|
||||||
|
|
||||||
public MPJSONFile(final MPFileUser user)
|
protected static final ObjectMapper objectMapper = new ObjectMapper();
|
||||||
throws MPKeyUnavailableException {
|
|
||||||
// Section: "export"
|
|
||||||
Export fileExport = this.export = new Export();
|
|
||||||
fileExport.format = 1;
|
|
||||||
fileExport.redacted = user.getContentMode().isRedacted();
|
|
||||||
fileExport.date = MPConstant.dateTimeFormatter.print( new Instant() );
|
|
||||||
|
|
||||||
// Section: "user"
|
static {
|
||||||
User fileUser = this.user = new User();
|
objectMapper.setSerializationInclusion( JsonInclude.Include.NON_EMPTY );
|
||||||
fileUser.avatar = user.getAvatar();
|
objectMapper.setVisibility( PropertyAccessor.FIELD, JsonAutoDetect.Visibility.NON_PRIVATE );
|
||||||
fileUser.full_name = user.getFullName();
|
|
||||||
|
|
||||||
fileUser.last_used = MPConstant.dateTimeFormatter.print( user.getLastUsed() );
|
|
||||||
fileUser.key_id = CodeUtils.encodeHex( user.getKeyID() );
|
|
||||||
|
|
||||||
fileUser.algorithm = user.getAlgorithm().version();
|
|
||||||
fileUser.default_type = user.getDefaultType();
|
|
||||||
|
|
||||||
// Section "sites"
|
|
||||||
sites = new LinkedHashMap<>();
|
|
||||||
for (final MPFileSite site : user.getSites()) {
|
|
||||||
Site fileSite;
|
|
||||||
String content = null, loginContent = null;
|
|
||||||
if (!fileExport.redacted) {
|
|
||||||
// Clear Text
|
|
||||||
content = site.getResult();
|
|
||||||
loginContent = user.getMasterKey().siteResult(
|
|
||||||
site.getSiteName(), site.getAlgorithm().mpw_default_counter(),
|
|
||||||
MPKeyPurpose.Identification, null, site.getLoginType(), site.getLoginState(), site.getAlgorithm() );
|
|
||||||
} else {
|
|
||||||
// Redacted
|
|
||||||
if (site.getResultType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
|
||||||
content = site.getSiteState();
|
|
||||||
if (site.getLoginType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
|
||||||
loginContent = site.getLoginState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sites.put( site.getSiteName(), fileSite = new Site() );
|
public MPJSONFile write(final MPFileUser modelUser)
|
||||||
fileSite.type = site.getResultType();
|
throws MPKeyUnavailableException {
|
||||||
fileSite.counter = site.getSiteCounter().longValue();
|
// Section: "export"
|
||||||
fileSite.algorithm = site.getAlgorithm().version();
|
if (export == null)
|
||||||
fileSite.password = content;
|
export = new Export();
|
||||||
fileSite.login_name = loginContent;
|
export.format = 1;
|
||||||
fileSite.login_type = site.getLoginType();
|
export.redacted = modelUser.getContentMode().isRedacted();
|
||||||
|
export.date = MPConstant.dateTimeFormatter.print( new Instant() );
|
||||||
|
|
||||||
fileSite.uses = site.getUses();
|
// Section: "user"
|
||||||
fileSite.last_used = MPConstant.dateTimeFormatter.print( site.getLastUsed() );
|
if (user == null)
|
||||||
|
user = new User();
|
||||||
|
user.avatar = modelUser.getAvatar();
|
||||||
|
user.full_name = modelUser.getFullName();
|
||||||
|
user.last_used = MPConstant.dateTimeFormatter.print( modelUser.getLastUsed() );
|
||||||
|
user.key_id = CodeUtils.encodeHex( modelUser.getKeyID() );
|
||||||
|
user.algorithm = modelUser.getAlgorithm().version();
|
||||||
|
user.default_type = modelUser.getDefaultType();
|
||||||
|
|
||||||
fileSite._ext_mpw = new Site.Ext();
|
// Section "sites"
|
||||||
fileSite._ext_mpw.url = site.getUrl();
|
if (sites == null)
|
||||||
|
sites = new LinkedHashMap<>();
|
||||||
|
for (final MPFileSite modelSite : modelUser.getSites()) {
|
||||||
|
String content = null, loginContent = null;
|
||||||
|
if (!export.redacted) {
|
||||||
|
// Clear Text
|
||||||
|
content = modelSite.getResult();
|
||||||
|
loginContent = modelUser.getMasterKey().siteResult(
|
||||||
|
modelSite.getSiteName(), modelSite.getAlgorithm().mpw_default_counter(),
|
||||||
|
MPKeyPurpose.Identification, null, modelSite.getLoginType(), modelSite.getLoginState(), modelSite.getAlgorithm() );
|
||||||
|
} else {
|
||||||
|
// Redacted
|
||||||
|
if (modelSite.getResultType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
||||||
|
content = modelSite.getSiteState();
|
||||||
|
if (modelSite.getLoginType().supportsTypeFeature( MPSiteFeature.ExportContent ))
|
||||||
|
loginContent = modelSite.getLoginState();
|
||||||
|
}
|
||||||
|
|
||||||
fileSite.questions = new LinkedHashMap<>();
|
Site site = sites.get( modelSite.getSiteName() );
|
||||||
|
if (site == null)
|
||||||
|
sites.put( modelSite.getSiteName(), site = new Site() );
|
||||||
|
site.type = modelSite.getResultType();
|
||||||
|
site.counter = modelSite.getSiteCounter().longValue();
|
||||||
|
site.algorithm = modelSite.getAlgorithm().version();
|
||||||
|
site.password = content;
|
||||||
|
site.login_name = loginContent;
|
||||||
|
site.login_type = modelSite.getLoginType();
|
||||||
|
|
||||||
|
site.uses = modelSite.getUses();
|
||||||
|
site.last_used = MPConstant.dateTimeFormatter.print( modelSite.getLastUsed() );
|
||||||
|
|
||||||
|
if (site._ext_mpw == null)
|
||||||
|
site._ext_mpw = new Site.Ext();
|
||||||
|
site._ext_mpw.url = modelSite.getUrl();
|
||||||
|
|
||||||
|
if (site.questions == null)
|
||||||
|
site.questions = new LinkedHashMap<>();
|
||||||
// for (size_t q = 0; q < site.questions_count; ++q) {
|
// for (size_t q = 0; q < site.questions_count; ++q) {
|
||||||
// MPMarshalledQuestion *question = &site.questions[q];
|
// MPMarshalledQuestion *question = &site.questions[q];
|
||||||
// if (!question.keyword)
|
// if (!question.keyword)
|
||||||
@ -112,37 +131,44 @@ public class MPJSONFile {
|
|||||||
// if (site.url)
|
// if (site.url)
|
||||||
// json_object_object_add( json_site_mpw, "url", site.url );
|
// json_object_object_add( json_site_mpw, "url", site.url );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPFileUser toUser(@Nullable final char[] masterPassword)
|
public MPFileUser read(@Nullable final char[] masterPassword)
|
||||||
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException {
|
throws MPIncorrectMasterPasswordException, MPKeyUnavailableException {
|
||||||
MPFileUser user = new MPFileUser(
|
MPAlgorithm algorithm = ifNotNullElse( user.algorithm, MPMasterKey.Version.CURRENT ).getAlgorithm();
|
||||||
this.user.full_name, CodeUtils.decodeHex( this.user.key_id ), this.user.algorithm.getAlgorithm(),
|
MPFileUser model = new MPFileUser(
|
||||||
this.user.avatar, this.user.default_type, MPConstant.dateTimeFormatter.parseDateTime( this.user.last_used ),
|
user.full_name, CodeUtils.decodeHex( user.key_id ), algorithm, user.avatar,
|
||||||
|
(user.default_type != null)? user.default_type: algorithm.mpw_default_password_type(),
|
||||||
|
(user.last_used != null)? MPConstant.dateTimeFormatter.parseDateTime( user.last_used ): new Instant(),
|
||||||
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
MPMarshalFormat.JSON, export.redacted? MPMarshaller.ContentMode.PROTECTED: MPMarshaller.ContentMode.VISIBLE );
|
||||||
|
model.setJSON( this );
|
||||||
if (masterPassword != null)
|
if (masterPassword != null)
|
||||||
user.authenticate( masterPassword );
|
model.authenticate( masterPassword );
|
||||||
|
|
||||||
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(
|
||||||
user, siteName, export.redacted? fileSite.password: null, UnsignedInteger.valueOf( fileSite.counter ),
|
model, siteName, export.redacted? fileSite.password: null, UnsignedInteger.valueOf( fileSite.counter ),
|
||||||
fileSite.type, fileSite.algorithm.getAlgorithm(),
|
fileSite.type, fileSite.algorithm.getAlgorithm(),
|
||||||
export.redacted? fileSite.login_name: null, fileSite.login_type,
|
export.redacted? fileSite.login_name: null, fileSite.login_type,
|
||||||
fileSite._ext_mpw.url, fileSite.uses, MPConstant.dateTimeFormatter.parseDateTime( fileSite.last_used ) );
|
(fileSite._ext_mpw != null)? fileSite._ext_mpw.url: null, fileSite.uses,
|
||||||
|
(fileSite.last_used != null)? MPConstant.dateTimeFormatter.parseDateTime( fileSite.last_used ): new Instant() );
|
||||||
|
|
||||||
if (!export.redacted) {
|
if (!export.redacted) {
|
||||||
if (fileSite.password != null)
|
if (fileSite.password != null)
|
||||||
site.setSitePassword( fileSite.type, fileSite.password );
|
site.setSitePassword( (fileSite.type != null)? fileSite.type: MPResultType.StoredPersonal, fileSite.password );
|
||||||
if (fileSite.login_name != null)
|
if (fileSite.login_name != null)
|
||||||
site.setLoginName( fileSite.login_type, fileSite.login_name );
|
site.setLoginName( (fileSite.login_type != null)? fileSite.login_type: MPResultType.StoredPersonal,
|
||||||
|
fileSite.login_name );
|
||||||
}
|
}
|
||||||
|
|
||||||
user.addSite( site );
|
model.addSite( site );
|
||||||
}
|
}
|
||||||
|
|
||||||
return user;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Data
|
// -- Data
|
||||||
@ -152,27 +178,32 @@ public class MPJSONFile {
|
|||||||
Map<String, Site> sites;
|
Map<String, Site> sites;
|
||||||
|
|
||||||
|
|
||||||
public static class Export {
|
public static class Export extends MPJSONAnyObject {
|
||||||
|
|
||||||
int format;
|
int format;
|
||||||
boolean redacted;
|
boolean redacted;
|
||||||
|
@Nullable
|
||||||
String date;
|
String date;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class User {
|
public static class User extends MPJSONAnyObject {
|
||||||
|
|
||||||
int avatar;
|
int avatar;
|
||||||
String full_name;
|
String full_name;
|
||||||
String last_used;
|
String last_used;
|
||||||
|
@Nullable
|
||||||
String key_id;
|
String key_id;
|
||||||
|
@Nullable
|
||||||
MPMasterKey.Version algorithm;
|
MPMasterKey.Version algorithm;
|
||||||
|
@Nullable
|
||||||
MPResultType default_type;
|
MPResultType default_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class Site {
|
public static class Site extends MPJSONAnyObject {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
MPResultType type;
|
MPResultType type;
|
||||||
long counter;
|
long counter;
|
||||||
MPMasterKey.Version algorithm;
|
MPMasterKey.Version algorithm;
|
||||||
@ -180,26 +211,32 @@ public class MPJSONFile {
|
|||||||
String password;
|
String password;
|
||||||
@Nullable
|
@Nullable
|
||||||
String login_name;
|
String login_name;
|
||||||
|
@Nullable
|
||||||
MPResultType login_type;
|
MPResultType login_type;
|
||||||
int uses;
|
int uses;
|
||||||
|
@Nullable
|
||||||
String last_used;
|
String last_used;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
Map<String, Question> questions;
|
Map<String, Question> questions;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
Ext _ext_mpw;
|
Ext _ext_mpw;
|
||||||
|
|
||||||
|
public static class Ext extends MPJSONAnyObject {
|
||||||
public static class Ext {
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
String url;
|
String url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static class Question {
|
public static class Question extends MPJSONAnyObject {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
MPResultType type;
|
MPResultType type;
|
||||||
|
@Nullable
|
||||||
String answer;
|
String answer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import static com.lyndir.masterpassword.model.MPJSONFile.objectMapper;
|
||||||
import com.lyndir.masterpassword.*;
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
|
|
||||||
|
|
||||||
@ -28,17 +30,16 @@ import javax.annotation.Nonnull;
|
|||||||
*/
|
*/
|
||||||
public class MPJSONMarshaller implements MPMarshaller {
|
public class MPJSONMarshaller implements MPMarshaller {
|
||||||
|
|
||||||
private final Gson gson = new GsonBuilder()
|
|
||||||
.registerTypeAdapter( MPMasterKey.Version.class, new EnumOrdinalAdapter() )
|
|
||||||
.registerTypeAdapter( MPResultType.class, new MPResultTypeAdapter() )
|
|
||||||
.setFieldNamingStrategy( FieldNamingPolicy.IDENTITY )
|
|
||||||
.setPrettyPrinting().create();
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String marshall(final MPFileUser user)
|
public String marshall(final MPFileUser user)
|
||||||
throws MPKeyUnavailableException, MPMarshalException {
|
throws MPKeyUnavailableException, MPMarshalException {
|
||||||
|
|
||||||
return gson.toJson( new MPJSONFile( user ) );
|
try {
|
||||||
|
return objectMapper.writeValueAsString( user.getJSON().write( user ) );
|
||||||
|
}
|
||||||
|
catch (final JsonProcessingException e) {
|
||||||
|
throw new MPMarshalException( "Couldn't compose JSON for: " + user, e );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,13 @@
|
|||||||
|
|
||||||
package com.lyndir.masterpassword.model;
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
import com.google.gson.*;
|
import static com.lyndir.masterpassword.model.MPJSONFile.objectMapper;
|
||||||
import com.lyndir.masterpassword.*;
|
|
||||||
import java.io.*;
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import com.fasterxml.jackson.databind.JsonMappingException;
|
||||||
|
import com.lyndir.masterpassword.MPKeyUnavailableException;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
@ -31,24 +34,19 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public class MPJSONUnmarshaller implements MPUnmarshaller {
|
public class MPJSONUnmarshaller implements MPUnmarshaller {
|
||||||
|
|
||||||
private final Gson gson = new GsonBuilder()
|
|
||||||
.registerTypeAdapter( MPMasterKey.Version.class, new EnumOrdinalAdapter() )
|
|
||||||
.registerTypeAdapter( MPResultType.class, new MPResultTypeAdapter() )
|
|
||||||
.setFieldNamingStrategy( FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES )
|
|
||||||
.setPrettyPrinting().create();
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
public MPFileUser unmarshall(@Nonnull final File file, @Nullable final char[] masterPassword)
|
||||||
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException {
|
throws IOException, MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException {
|
||||||
|
|
||||||
try (Reader reader = new InputStreamReader( new FileInputStream( file ), StandardCharsets.UTF_8 )) {
|
|
||||||
try {
|
try {
|
||||||
return gson.fromJson( reader, MPJSONFile.class ).toUser( masterPassword );
|
return objectMapper.readValue( file, MPJSONFile.class ).read( masterPassword );
|
||||||
}
|
}
|
||||||
catch (final JsonSyntaxException e) {
|
catch (final JsonParseException e) {
|
||||||
throw new MPMarshalException( "Couldn't parse JSON in: " + file, 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 );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,10 +56,16 @@ public class MPJSONUnmarshaller implements MPUnmarshaller {
|
|||||||
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException {
|
throws MPMarshalException, MPIncorrectMasterPasswordException, MPKeyUnavailableException {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return gson.fromJson( content, MPJSONFile.class ).toUser( masterPassword );
|
return objectMapper.readValue( content, MPJSONFile.class ).read( masterPassword );
|
||||||
}
|
}
|
||||||
catch (final JsonSyntaxException e) {
|
catch (final JsonParseException e) {
|
||||||
throw new MPMarshalException( "Couldn't parse JSON", e );
|
throw new MPMarshalException( "Couldn't parse JSON.", e );
|
||||||
|
}
|
||||||
|
catch (final JsonMappingException e) {
|
||||||
|
throw new MPMarshalException( "Couldn't map JSON.", e );
|
||||||
|
}
|
||||||
|
catch (final IOException e) {
|
||||||
|
throw new MPMarshalException( "Couldn't read JSON.", e );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
67
core/java/tests/src/main/resources/test.mpsites.json
Normal file
67
core/java/tests/src/main/resources/test.mpsites.json
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"export": {
|
||||||
|
"format": 1,
|
||||||
|
"redacted": true,
|
||||||
|
"date": "2018-05-10T03:41:18Z",
|
||||||
|
"_ext_mpw": {
|
||||||
|
"save": "me"
|
||||||
|
},
|
||||||
|
"_ext_other": {
|
||||||
|
"save": "me"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"avatar": 3,
|
||||||
|
"full_name": "Robert Lee Mitchell",
|
||||||
|
"last_used": "2018-05-10T03:41:18Z",
|
||||||
|
"key_id": "98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302",
|
||||||
|
"algorithm": 3,
|
||||||
|
"default_type": 17,
|
||||||
|
"_ext_mpw": {
|
||||||
|
"save": "me"
|
||||||
|
},
|
||||||
|
"_ext_other": {
|
||||||
|
"save": "me"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sites": {
|
||||||
|
"masterpasswordapp.com": {
|
||||||
|
"type": 17,
|
||||||
|
"counter": 1,
|
||||||
|
"algorithm": 3,
|
||||||
|
"login_type": 30,
|
||||||
|
"uses": 2,
|
||||||
|
"last_used": "2018-05-10T03:41:18Z",
|
||||||
|
"questions": {
|
||||||
|
"": {
|
||||||
|
"type": 31
|
||||||
|
},
|
||||||
|
"mother": {
|
||||||
|
"type": 31
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_ext_mpw": {
|
||||||
|
"url": "https://masterpasswordapp.com",
|
||||||
|
"save": "me"
|
||||||
|
},
|
||||||
|
"_ext_other": {
|
||||||
|
"save": "me"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"personal.site": {
|
||||||
|
"type": 1056,
|
||||||
|
"counter": 1,
|
||||||
|
"algorithm": 3,
|
||||||
|
"password": "ZTgr4cY6L28wG7DsO+iz\/hrTQxM3UHz0x8ZU99LjgxjHG+bLIJygkbg\/7HdjEIFH6A3z+Dt2H1gpt9yPyQGZcewTiPXJX0pNpVsIKAAdzVNcUfYoqkWjoFRoZD7sM\/ctxWDH4JUuJ+rjoBkWtRLK9kYBvu7UD1QdlEZI\/wPKv1A=",
|
||||||
|
"login_type": 30,
|
||||||
|
"uses": 1,
|
||||||
|
"last_used": "2018-05-10T03:48:35Z"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"_ext_mpw": {
|
||||||
|
"save": "me"
|
||||||
|
},
|
||||||
|
"_ext_other": {
|
||||||
|
"save": "me"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user