A persistence model for the Java implementation and ability to parse in and write out export files.
This commit is contained in:
parent
3f4558da2b
commit
4ff8cd6d90
@ -7,8 +7,23 @@ package com.lyndir.masterpassword;
|
|||||||
*/
|
*/
|
||||||
public enum MPSiteFeature {
|
public enum MPSiteFeature {
|
||||||
|
|
||||||
/** Export the key-protected content data. */
|
/**
|
||||||
ExportContent,
|
* Export the key-protected content data.
|
||||||
/** Never export content. */
|
*/
|
||||||
DevicePrivate,
|
ExportContent( 1 << 10 ),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Never export content.
|
||||||
|
*/
|
||||||
|
DevicePrivate( 1 << 11 );
|
||||||
|
|
||||||
|
MPSiteFeature(final int mask) {
|
||||||
|
this.mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int mask;
|
||||||
|
|
||||||
|
public int getMask() {
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableSet;
|
|||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,11 +16,12 @@ import java.util.Set;
|
|||||||
public enum MPSiteType {
|
public enum MPSiteType {
|
||||||
|
|
||||||
GeneratedMaximum( "20 characters, contains symbols.", //
|
GeneratedMaximum( "20 characters, contains symbols.", //
|
||||||
ImmutableList.of( "x", "max", "maximum" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "x", "max", "maximum" ), //
|
||||||
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ) ),
|
ImmutableList.of( new MPTemplate( "anoxxxxxxxxxxxxxxxxx" ), new MPTemplate( "axxxxxxxxxxxxxxxxxno" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0x0 ),
|
||||||
|
|
||||||
GeneratedLong( "Copy-friendly, 14 characters, contains symbols.", //
|
GeneratedLong( "Copy-friendly, 14 characters, contains symbols.", //
|
||||||
ImmutableList.of( "l", "long" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "l", "long" ), //
|
||||||
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
ImmutableList.of( new MPTemplate( "CvcvnoCvcvCvcv" ), new MPTemplate( "CvcvCvcvnoCvcv" ),
|
||||||
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
new MPTemplate( "CvcvCvcvCvcvno" ), new MPTemplate( "CvccnoCvcvCvcv" ),
|
||||||
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
|
new MPTemplate( "CvccCvcvnoCvcv" ), new MPTemplate( "CvccCvcvCvcvno" ),
|
||||||
@ -30,56 +32,67 @@ public enum MPSiteType {
|
|||||||
new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
|
new MPTemplate( "CvccCvccCvcvno" ), new MPTemplate( "CvcvnoCvccCvcc" ),
|
||||||
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
|
new MPTemplate( "CvcvCvccnoCvcc" ), new MPTemplate( "CvcvCvccCvccno" ),
|
||||||
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
|
new MPTemplate( "CvccnoCvcvCvcc" ), new MPTemplate( "CvccCvcvnoCvcc" ),
|
||||||
new MPTemplate( "CvccCvcvCvccno" ) ) ),
|
new MPTemplate( "CvccCvcvCvccno" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0x1 ),
|
||||||
|
|
||||||
GeneratedMedium( "Copy-friendly, 8 characters, contains symbols.", //
|
GeneratedMedium( "Copy-friendly, 8 characters, contains symbols.", //
|
||||||
ImmutableList.of( "m", "med", "medium" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "m", "med", "medium" ), //
|
||||||
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ) ),
|
ImmutableList.of( new MPTemplate( "CvcnoCvc" ), new MPTemplate( "CvcCvcno" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0x2 ),
|
||||||
|
|
||||||
GeneratedBasic( "8 characters, no symbols.", //
|
GeneratedBasic( "8 characters, no symbols.", //
|
||||||
ImmutableList.of( "b", "basic" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "b", "basic" ), //
|
||||||
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ) ),
|
ImmutableList.of( new MPTemplate( "aaanaaan" ), new MPTemplate( "aannaaan" ), new MPTemplate( "aaannaaa" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0x3 ),
|
||||||
|
|
||||||
GeneratedShort( "Copy-friendly, 4 characters, no symbols.", //
|
GeneratedShort( "Copy-friendly, 4 characters, no symbols.", //
|
||||||
ImmutableList.of( "s", "short" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "s", "short" ), //
|
||||||
ImmutableList.of( new MPTemplate( "Cvcn" ) ) ),
|
ImmutableList.of( new MPTemplate( "Cvcn" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0x4 ),
|
||||||
|
|
||||||
GeneratedPIN( "4 numbers.", //
|
GeneratedPIN( "4 numbers.", //
|
||||||
ImmutableList.of( "i", "pin" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "i", "pin" ), //
|
||||||
ImmutableList.of( new MPTemplate( "nnnn" ) ) ),
|
ImmutableList.of( new MPTemplate( "nnnn" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0x5 ),
|
||||||
|
|
||||||
GeneratedName( "9 letter name.", //
|
GeneratedName( "9 letter name.", //
|
||||||
ImmutableList.of( "n", "name" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "n", "name" ), //
|
||||||
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ) ),
|
ImmutableList.of( new MPTemplate( "cvccvcvcv" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0xE ),
|
||||||
|
|
||||||
GeneratedPhrase( "20 character sentence.", //
|
GeneratedPhrase( "20 character sentence.", //
|
||||||
ImmutableList.of( "p", "phrase" ), MPSiteTypeClass.Generated, //
|
ImmutableList.of( "p", "phrase" ), //
|
||||||
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
ImmutableList.of( new MPTemplate( "cvcc cvc cvccvcv cvc" ), new MPTemplate( "cvc cvccvcvcv cvcv" ),
|
||||||
new MPTemplate( "cv cvccv cvc cvcvccv" ) ) ),
|
new MPTemplate( "cv cvccv cvc cvcvccv" ) ), //
|
||||||
|
MPSiteTypeClass.Generated, 0xF ),
|
||||||
|
|
||||||
StoredPersonal( "AES-encrypted, exportable.", //
|
StoredPersonal( "AES-encrypted, exportable.", //
|
||||||
ImmutableList.of( "personal" ), MPSiteTypeClass.Stored, //
|
ImmutableList.of( "personal" ), //
|
||||||
ImmutableList.<MPTemplate>of(), MPSiteFeature.ExportContent ),
|
ImmutableList.<MPTemplate>of(), //
|
||||||
|
MPSiteTypeClass.Stored, 0x0, MPSiteFeature.ExportContent ),
|
||||||
|
|
||||||
StoredDevicePrivate( "AES-encrypted, not exported.", //
|
StoredDevicePrivate( "AES-encrypted, not exported.", //
|
||||||
ImmutableList.of( "device" ), MPSiteTypeClass.Stored, //
|
ImmutableList.of( "device" ), //
|
||||||
ImmutableList.<MPTemplate>of(), MPSiteFeature.DevicePrivate );
|
ImmutableList.<MPTemplate>of(), //
|
||||||
|
MPSiteTypeClass.Stored, 0x1, MPSiteFeature.DevicePrivate );
|
||||||
|
|
||||||
static final Logger logger = Logger.get( MPSiteType.class );
|
static final Logger logger = Logger.get( MPSiteType.class );
|
||||||
|
|
||||||
private final String description;
|
private final String description;
|
||||||
private final List<String> options;
|
private final List<String> options;
|
||||||
private final MPSiteTypeClass typeClass;
|
|
||||||
private final List<MPTemplate> templates;
|
private final List<MPTemplate> templates;
|
||||||
|
private final MPSiteTypeClass typeClass;
|
||||||
|
private final int typeIndex;
|
||||||
private final Set<MPSiteFeature> typeFeatures;
|
private final Set<MPSiteFeature> typeFeatures;
|
||||||
|
|
||||||
MPSiteType(final String description, final List<String> options, final MPSiteTypeClass typeClass, final List<MPTemplate> templates,
|
MPSiteType(final String description, final List<String> options, final List<MPTemplate> templates, final MPSiteTypeClass typeClass,
|
||||||
final MPSiteFeature... typeFeatures) {
|
final int typeIndex, final MPSiteFeature... typeFeatures) {
|
||||||
|
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.typeClass = typeClass;
|
|
||||||
this.templates = templates;
|
this.templates = templates;
|
||||||
|
this.typeClass = typeClass;
|
||||||
|
this.typeIndex = typeIndex;
|
||||||
|
|
||||||
ImmutableSet.Builder<MPSiteFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
ImmutableSet.Builder<MPSiteFeature> typeFeaturesBuilder = ImmutableSet.builder();
|
||||||
for (final MPSiteFeature typeFeature : typeFeatures) {
|
for (final MPSiteFeature typeFeature : typeFeatures) {
|
||||||
@ -107,6 +120,14 @@ public enum MPSiteType {
|
|||||||
return typeFeatures;
|
return typeFeatures;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getMask() {
|
||||||
|
int mask = typeIndex | typeClass.getMask();
|
||||||
|
for (MPSiteFeature typeFeature : typeFeatures)
|
||||||
|
mask |= typeFeature.getMask();
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param option The option to select a type with. It is matched case insensitively.
|
* @param option The option to select a type with. It is matched case insensitively.
|
||||||
*
|
*
|
||||||
@ -153,6 +174,21 @@ public enum MPSiteType {
|
|||||||
return types.build();
|
return types.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mask The mask for which we look up types.
|
||||||
|
*
|
||||||
|
* @return All types that support the given mask.
|
||||||
|
*/
|
||||||
|
public static ImmutableList<MPSiteType> forMask(final int mask) {
|
||||||
|
|
||||||
|
ImmutableList.Builder<MPSiteType> types = ImmutableList.builder();
|
||||||
|
for (MPSiteType siteType : values())
|
||||||
|
if ((siteType.getMask() & mask) != 0)
|
||||||
|
types.add( siteType );
|
||||||
|
|
||||||
|
return types.build();
|
||||||
|
}
|
||||||
|
|
||||||
public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
|
public MPTemplate getTemplateAtRollingIndex(final int templateIndex) {
|
||||||
return templates.get( templateIndex % templates.size() );
|
return templates.get( templateIndex % templates.size() );
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,16 @@ package com.lyndir.masterpassword;
|
|||||||
* @author lhunath
|
* @author lhunath
|
||||||
*/
|
*/
|
||||||
public enum MPSiteTypeClass {
|
public enum MPSiteTypeClass {
|
||||||
Generated,
|
Generated( 1 << 4 ),
|
||||||
Stored
|
Stored( 1 << 5 );
|
||||||
|
|
||||||
|
private final int mask;
|
||||||
|
|
||||||
|
MPSiteTypeClass(final int mask) {
|
||||||
|
this.mask = mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMask() {
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,9 @@ import javax.annotation.Nullable;
|
|||||||
*/
|
*/
|
||||||
public class MasterKey {
|
public class MasterKey {
|
||||||
|
|
||||||
|
public static final int ALGORITHM = 1;
|
||||||
|
public static final String VERSION = "2.1";
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( MasterKey.class );
|
private static final Logger logger = Logger.get( MasterKey.class );
|
||||||
private static final int MP_N = 32768;
|
private static final int MP_N = 32768;
|
||||||
@ -103,9 +106,9 @@ public class MasterKey {
|
|||||||
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
|
byte[] siteContextBytes = siteContext == null? null: siteContext.getBytes( MP_charset );
|
||||||
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
|
byte[] siteContextLengthBytes = bytesForInt( siteContextBytes == null? 0: siteContextBytes.length );
|
||||||
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
|
logger.trc( "site scope: %s, context: %s", siteScope, siteContext == null? "<empty>": siteContext );
|
||||||
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope,
|
logger.trc( "seed from: hmac-sha256(masterKey, %s | %s | %s | %s | %s | %s)", siteScope, CodeUtils.encodeHex( siteNameLengthBytes ),
|
||||||
CodeUtils.encodeHex( siteNameLengthBytes ), siteName, CodeUtils.encodeHex( siteCounterBytes ),
|
siteName, CodeUtils.encodeHex( siteCounterBytes ), CodeUtils.encodeHex( siteContextLengthBytes ),
|
||||||
CodeUtils.encodeHex( siteContextLengthBytes ), siteContext == null? "(null)": siteContext );
|
siteContext == null? "(null)": siteContext );
|
||||||
|
|
||||||
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
byte[] sitePasswordInfo = Bytes.concat( siteScope.getBytes( MP_charset ), siteNameLengthBytes, siteNameBytes, siteCounterBytes );
|
||||||
logger.trc( "sitePasswordInfo ID: %s", idForBytes( sitePasswordInfo ) );
|
logger.trc( "sitePasswordInfo ID: %s", idForBytes( sitePasswordInfo ) );
|
||||||
|
@ -86,8 +86,7 @@ public class EmergencyActivity extends Activity {
|
|||||||
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
sitePasswordField.setTypeface( Res.sourceCodePro_Black );
|
||||||
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
sitePasswordField.setPaintFlags( userNameField.getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG );
|
||||||
|
|
||||||
typeField.setAdapter(
|
typeField.setAdapter( new ArrayAdapter<>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
|
||||||
new ArrayAdapter<MPSiteType>( this, R.layout.type_item, MPSiteType.forClass( MPSiteTypeClass.Generated ) ) );
|
|
||||||
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
|
typeField.setSelection( MPSiteType.GeneratedLong.ordinal() );
|
||||||
|
|
||||||
counterField.setMinValue( 1 );
|
counterField.setMinValue( 1 );
|
||||||
@ -129,7 +128,7 @@ public class EmergencyActivity extends Activity {
|
|||||||
|
|
||||||
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit();
|
SharedPreferences.Editor pref = getPreferences( MODE_PRIVATE ).edit();
|
||||||
pref.putString( "userName", userName );
|
pref.putString( "userName", userName );
|
||||||
pref.commit();
|
pref.apply();
|
||||||
|
|
||||||
if (masterKeyFuture != null)
|
if (masterKeyFuture != null)
|
||||||
masterKeyFuture.cancel( true );
|
masterKeyFuture.cancel( true );
|
||||||
@ -184,7 +183,7 @@ public class EmergencyActivity extends Activity {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, variant, context );
|
final String sitePassword = masterKeyFuture.get().encode( siteName, type, counter, MPSiteVariant.Password, null );
|
||||||
|
|
||||||
runOnUiThread( new Runnable() {
|
runOnUiThread( new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -73,10 +73,11 @@
|
|||||||
<!-- PROJECT REFERENCES -->
|
<!-- PROJECT REFERENCES -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.masterpassword</groupId>
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
<artifactId>masterpassword-algorithm</artifactId>
|
<artifactId>masterpassword-model</artifactId>
|
||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- EXTERNAL DEPENDENCIES -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ch.qos.logback</groupId>
|
<groupId>ch.qos.logback</groupId>
|
||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
|
44
MasterPassword/Java/masterpassword-model/pom.xml
Normal file
44
MasterPassword/Java/masterpassword-model/pom.xml
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<!-- PROJECT METADATA -->
|
||||||
|
<parent>
|
||||||
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword</artifactId>
|
||||||
|
<version>GIT-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<name>Master Password Site Model</name>
|
||||||
|
<description>A persistence model for Master Password sites.</description>
|
||||||
|
|
||||||
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword-model</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<!-- DEPENDENCY MANAGEMENT -->
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- PROJECT REFERENCES -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword-algorithm</artifactId>
|
||||||
|
<version>GIT-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- TESTING -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.testng</groupId>
|
||||||
|
<artifactId>testng</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ch.qos.logback</groupId>
|
||||||
|
<artifactId>logback-classic</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,115 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
|
import com.lyndir.masterpassword.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-05
|
||||||
|
*/
|
||||||
|
public class MPSite {
|
||||||
|
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong;
|
||||||
|
public static final int DEFAULT_COUNTER = 1;
|
||||||
|
|
||||||
|
private int mpVersion;
|
||||||
|
private DateTime lastUsed;
|
||||||
|
private String siteName;
|
||||||
|
private MPSiteType siteType;
|
||||||
|
private int siteCounter;
|
||||||
|
private int uses;
|
||||||
|
private String loginName;
|
||||||
|
|
||||||
|
public MPSite(final String siteName) {
|
||||||
|
this( siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPSite(final String siteName, final MPSiteType siteType, final int siteCounter) {
|
||||||
|
this.siteName = siteName;
|
||||||
|
this.siteType = siteType;
|
||||||
|
this.siteCounter = siteCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MPSite(final int mpVersion, final DateTime lastUsed, final String siteName, final MPSiteType siteType, final int siteCounter,
|
||||||
|
final int uses, final String loginName, final String importContent) {
|
||||||
|
this.mpVersion = mpVersion;
|
||||||
|
this.lastUsed = lastUsed;
|
||||||
|
this.siteName = siteName;
|
||||||
|
this.siteType = siteType;
|
||||||
|
this.siteCounter = siteCounter;
|
||||||
|
this.uses = uses;
|
||||||
|
this.loginName = loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resultFor(final MasterKey masterKey) {
|
||||||
|
return resultFor( masterKey, MPSiteVariant.Password, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resultFor(final MasterKey masterKey, final MPSiteVariant variant, final String context) {
|
||||||
|
return masterKey.encode( siteName, siteType, siteCounter, variant, context );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
protected String exportContent() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMPVersion() {
|
||||||
|
return mpVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMPVersion(final int mpVersion) {
|
||||||
|
this.mpVersion = mpVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime getLastUsed() {
|
||||||
|
return lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastUsed(final DateTime lastUsed) {
|
||||||
|
this.lastUsed = lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSiteName() {
|
||||||
|
return siteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSiteName(final String siteName) {
|
||||||
|
this.siteName = siteName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPSiteType getSiteType() {
|
||||||
|
return siteType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSiteType(final MPSiteType siteType) {
|
||||||
|
this.siteType = siteType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getSiteCounter() {
|
||||||
|
return siteCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSiteCounter(final int siteCounter) {
|
||||||
|
this.siteCounter = siteCounter;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getUses() {
|
||||||
|
return uses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUses(final int uses) {
|
||||||
|
this.uses = uses;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLoginName() {
|
||||||
|
return loginName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoginName(final String loginName) {
|
||||||
|
this.loginName = loginName;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import com.google.common.io.CharSink;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-07
|
||||||
|
*/
|
||||||
|
public class MPSiteFileManager extends MPSiteManager {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( MPSiteFileManager.class );
|
||||||
|
|
||||||
|
private final File file;
|
||||||
|
|
||||||
|
public static MPSiteFileManager create(final File file) {
|
||||||
|
try {
|
||||||
|
return new MPSiteFileManager( file );
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
throw logger.bug( e, "Unable to open sites from file: %s", file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MPSiteFileManager(final File file)
|
||||||
|
throws IOException {
|
||||||
|
|
||||||
|
super( MPSiteUnmarshaller.unmarshall( file ).getUser() );
|
||||||
|
this.file = file;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void save() {
|
||||||
|
try {
|
||||||
|
new CharSink() {
|
||||||
|
@Override
|
||||||
|
public Writer openStream()
|
||||||
|
throws IOException {
|
||||||
|
return new FileWriter( file );
|
||||||
|
}
|
||||||
|
}.write( MPSiteMarshaller.marshallSafe( getUser() ).getExport() );
|
||||||
|
}
|
||||||
|
catch (IOException e) {
|
||||||
|
logger.err( e, "Unable to save sites to file: %s", file );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getFile() {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-05
|
||||||
|
*/
|
||||||
|
public abstract class MPSiteManager {
|
||||||
|
|
||||||
|
private final MPUser user;
|
||||||
|
|
||||||
|
public MPSiteManager(final MPUser user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPUser getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<MPSiteResult> findSitesByName(String query) {
|
||||||
|
ImmutableList.Builder<MPSiteResult> results = ImmutableList.builder();
|
||||||
|
for (MPSite site : user.getSites())
|
||||||
|
if (site.getSiteName().startsWith( query ))
|
||||||
|
results.add( new MPSiteResult( site ) );
|
||||||
|
|
||||||
|
return results.build();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,130 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.lyndir.masterpassword.MasterKey;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import org.joda.time.Instant;
|
||||||
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
|
import org.joda.time.format.ISODateTimeFormat;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-07
|
||||||
|
*/
|
||||||
|
public class MPSiteMarshaller {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTime();
|
||||||
|
|
||||||
|
private final StringBuilder export = new StringBuilder();
|
||||||
|
private ContentMode contentMode = ContentMode.PROTECTED;
|
||||||
|
private MasterKey masterKey;
|
||||||
|
|
||||||
|
public static MPSiteMarshaller marshallSafe(final MPUser user) {
|
||||||
|
MPSiteMarshaller marshaller = new MPSiteMarshaller();
|
||||||
|
marshaller.marshallHeaderForSafeContent( user );
|
||||||
|
for (MPSite site : user.getSites())
|
||||||
|
marshaller.marshallSite( site );
|
||||||
|
|
||||||
|
return marshaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MPSiteMarshaller marshallVisible(final MPUser user, final MasterKey masterKey) {
|
||||||
|
MPSiteMarshaller marshaller = new MPSiteMarshaller();
|
||||||
|
marshaller.marshallHeaderForVisibleContentWithKey( user, masterKey );
|
||||||
|
for (MPSite site : user.getSites())
|
||||||
|
marshaller.marshallSite( site );
|
||||||
|
|
||||||
|
return marshaller;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String marshallHeaderForSafeContent(final MPUser user) {
|
||||||
|
return marshallHeader( ContentMode.PROTECTED, user, null );
|
||||||
|
}
|
||||||
|
|
||||||
|
private String marshallHeaderForVisibleContentWithKey(final MPUser user, final MasterKey masterKey) {
|
||||||
|
return marshallHeader( ContentMode.VISIBLE, user, masterKey );
|
||||||
|
}
|
||||||
|
|
||||||
|
private String marshallHeader(final ContentMode contentMode, final MPUser user, @Nullable final MasterKey masterKey) {
|
||||||
|
this.masterKey = masterKey;
|
||||||
|
|
||||||
|
StringBuilder header = new StringBuilder();
|
||||||
|
header.append( "# Master Password site export\n" );
|
||||||
|
header.append( "# " ).append( contentMode.description() ).append( '\n' );
|
||||||
|
header.append( "# \n" );
|
||||||
|
header.append( "##\n" );
|
||||||
|
header.append( "# Format: 1\n" );
|
||||||
|
header.append( "# Date: " ).append( rfc3339.print( new Instant() ) ).append( '\n' );
|
||||||
|
header.append( "# User Name: " ).append( user.getFullName() ).append( '\n' );
|
||||||
|
header.append( "# Full Name: " ).append( user.getFullName() ).append( '\n' );
|
||||||
|
header.append( "# Avatar: " ).append( user.getAvatar() ).append( '\n' );
|
||||||
|
header.append( "# Key ID: " ).append( user.exportKeyID() ).append( '\n' );
|
||||||
|
header.append( "# Version: " ).append( MasterKey.VERSION ).append( '\n' );
|
||||||
|
header.append( "# Algorithm: " ).append( MasterKey.ALGORITHM ).append( '\n' );
|
||||||
|
header.append( "# Default Type: " ).append( user.getDefaultType().getMask() ).append( '\n' );
|
||||||
|
header.append( "# Passwords: " ).append( contentMode.name() ).append( '\n' );
|
||||||
|
header.append( "##\n" );
|
||||||
|
header.append( "#\n" );
|
||||||
|
header.append( "# Last Times Password Login\t Site\tSite\n" );
|
||||||
|
header.append( "# used used type name\t name\tpassword\n" );
|
||||||
|
|
||||||
|
export.append( header );
|
||||||
|
return header.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String marshallSite(MPSite site) {
|
||||||
|
String exportLine = strf( "%s %8ld %8s %25s\t%25s\t%s", //
|
||||||
|
rfc3339.print( site.getLastUsed() ), // lastUsed
|
||||||
|
site.getUses(), // uses
|
||||||
|
strf( "%lu:%lu:%lu", //
|
||||||
|
site.getSiteType().getMask(), // type
|
||||||
|
site.getMPVersion(), // algorithm
|
||||||
|
site.getSiteCounter() ), // counter
|
||||||
|
ifNotNullElse( site.getLoginName(), "" ), // loginName
|
||||||
|
site.getSiteName(), // siteName
|
||||||
|
ifNotNullElse( contentMode.contentForSite( site, masterKey ), "" ) // password
|
||||||
|
);
|
||||||
|
export.append( exportLine ).append( '\n' );
|
||||||
|
|
||||||
|
return exportLine;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getExport() {
|
||||||
|
return export.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ContentMode getContentMode() {
|
||||||
|
return contentMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ContentMode {
|
||||||
|
PROTECTED( "Export of site names and stored passwords (unless device-private) encrypted with the master key." ) {
|
||||||
|
@Override
|
||||||
|
public String contentForSite(final MPSite site, @Nullable final MasterKey masterKey) {
|
||||||
|
return site.exportContent();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
VISIBLE( "Export of site names and passwords in clear-text." ) {
|
||||||
|
@Override
|
||||||
|
public String contentForSite(final MPSite site, @Nonnull final MasterKey masterKey) {
|
||||||
|
return site.resultFor( Preconditions.checkNotNull( masterKey, "Master key is required when content mode is VISIBLE." ) );
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private final String description;
|
||||||
|
|
||||||
|
ContentMode(final String description) {
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String description() {
|
||||||
|
return description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String contentForSite(final MPSite site, final MasterKey masterKey);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-07
|
||||||
|
*/
|
||||||
|
public class MPSiteResult {
|
||||||
|
|
||||||
|
private final MPSite site;
|
||||||
|
|
||||||
|
public MPSiteResult(final MPSite site) {
|
||||||
|
this.site = site;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPSite getSite() {
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,158 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.io.CharStreams;
|
||||||
|
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.lhunath.opal.system.util.NNOperation;
|
||||||
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
|
import java.io.*;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.joda.time.format.DateTimeFormatter;
|
||||||
|
import org.joda.time.format.ISODateTimeFormat;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-07
|
||||||
|
*/
|
||||||
|
public class MPSiteUnmarshaller {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( MPSite.class );
|
||||||
|
private static final DateTimeFormatter rfc3339 = ISODateTimeFormat.dateTime();
|
||||||
|
private static final Pattern[] unmarshallFormats = new Pattern[]{
|
||||||
|
Pattern.compile( "^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)? +([^\t]+)\t(.*)" ),
|
||||||
|
Pattern.compile( "^([^ ]+) +([[:digit:]]+) +([[:digit:]]+)(:[[:digit:]]+)?(:[[:digit:]]+)? +([^\t]*)\t *([^\t]+)\t(.*)" ) };
|
||||||
|
private static final Pattern headerFormat = Pattern.compile( "^#[[:space:]]*([^:]+): (.*)" );
|
||||||
|
|
||||||
|
private final int importFormat;
|
||||||
|
private final int mpVersion;
|
||||||
|
private final boolean clearContent;
|
||||||
|
private final MPUser user;
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static MPSiteUnmarshaller unmarshall(@Nonnull File file)
|
||||||
|
throws IOException {
|
||||||
|
try (Reader reader = new FileReader( file )) {
|
||||||
|
return unmarshall( CharStreams.readLines( reader ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
|
public static MPSiteUnmarshaller unmarshall(@Nonnull List<String> lines) {
|
||||||
|
byte[] keyID = null;
|
||||||
|
String fullName = null;
|
||||||
|
int mpVersion = 0, importFormat = 0, avatar = 0;
|
||||||
|
boolean clearContent = false, headerStarted = false;
|
||||||
|
MPSiteType defaultType = MPSiteType.GeneratedLong;
|
||||||
|
MPSiteUnmarshaller marshaller = null;
|
||||||
|
final ImmutableList.Builder<MPSite> sites = ImmutableList.builder();
|
||||||
|
|
||||||
|
for (String line : lines)
|
||||||
|
// Header delimitor.
|
||||||
|
if (line.startsWith( "##" ))
|
||||||
|
if (!headerStarted)
|
||||||
|
// Starts the header.
|
||||||
|
headerStarted = true;
|
||||||
|
else
|
||||||
|
// Ends the header.
|
||||||
|
marshaller = new MPSiteUnmarshaller( importFormat, mpVersion, fullName, keyID, avatar, defaultType, clearContent );
|
||||||
|
|
||||||
|
// Comment.
|
||||||
|
else if (line.startsWith( "#" )) {
|
||||||
|
if (headerStarted && marshaller == 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 = value.equalsIgnoreCase( "visible" );
|
||||||
|
else if ("Default Type".equalsIgnoreCase( name ))
|
||||||
|
defaultType = Iterables.getOnlyElement( MPSiteType.forMask( ConversionUtils.toIntegerNN( value ) ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No comment.
|
||||||
|
else if (marshaller != null)
|
||||||
|
ifNotNull( marshaller.unmarshallSite( line ), new NNOperation<MPSite>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@Nonnull final MPSite site) {
|
||||||
|
sites.add( site );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
return Preconditions.checkNotNull( marshaller, "No full header found in import file." );
|
||||||
|
}
|
||||||
|
|
||||||
|
protected MPSiteUnmarshaller(final int importFormat, final int mpVersion, final String fullName, final byte[] keyID, final int avatar,
|
||||||
|
final MPSiteType defaultType, final boolean clearContent) {
|
||||||
|
this.importFormat = importFormat;
|
||||||
|
this.mpVersion = mpVersion;
|
||||||
|
this.clearContent = clearContent;
|
||||||
|
|
||||||
|
user = new MPUser( fullName, keyID, avatar, defaultType, new DateTime( 0 ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public MPSite unmarshallSite(@Nonnull String siteLine) {
|
||||||
|
Matcher siteMatcher = unmarshallFormats[importFormat].matcher( siteLine );
|
||||||
|
if (!siteMatcher.matches())
|
||||||
|
return null;
|
||||||
|
|
||||||
|
MPSite site;
|
||||||
|
switch (importFormat) {
|
||||||
|
case 0:
|
||||||
|
site = new MPSite( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ), //
|
||||||
|
rfc3339.parseDateTime( siteMatcher.group( 1 ) ), //
|
||||||
|
siteMatcher.group( 5 ), //
|
||||||
|
Iterables.getOnlyElement( MPSiteType.forMask( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ) ),
|
||||||
|
MPSite.DEFAULT_COUNTER, //
|
||||||
|
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
||||||
|
null, //
|
||||||
|
siteMatcher.group( 6 ) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
site = new MPSite( ConversionUtils.toIntegerNN( siteMatcher.group( 4 ).replace( ":", "" ) ), //
|
||||||
|
rfc3339.parseDateTime( siteMatcher.group( 1 ) ), //
|
||||||
|
siteMatcher.group( 7 ), //
|
||||||
|
Iterables.getOnlyElement( MPSiteType.forMask( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ) ),
|
||||||
|
ConversionUtils.toIntegerNN( siteMatcher.group( 5 ).replace( ":", "" ) ), //
|
||||||
|
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
||||||
|
siteMatcher.group( 6 ), //
|
||||||
|
siteMatcher.group( 8 ) );
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw logger.bug( "Unexpected format: %d", importFormat );
|
||||||
|
}
|
||||||
|
|
||||||
|
user.addSite( site );
|
||||||
|
return site;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPUser getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.lyndir.masterpassword.model;
|
||||||
|
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
|
import java.util.*;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 14-12-07
|
||||||
|
*/
|
||||||
|
public class MPUser {
|
||||||
|
|
||||||
|
private final String fullName;
|
||||||
|
private final byte[] keyID;
|
||||||
|
private final int avatar;
|
||||||
|
private final MPSiteType defaultType;
|
||||||
|
private final DateTime lastUsed;
|
||||||
|
private final Collection<MPSite> sites = Sets.newHashSet();
|
||||||
|
|
||||||
|
public MPUser(final String fullName, final byte[] keyID) {
|
||||||
|
this( fullName, keyID, 0, MPSiteType.GeneratedLong, new DateTime() );
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPUser(final String fullName, final byte[] keyID, final int avatar, final MPSiteType defaultType, final DateTime lastUsed) {
|
||||||
|
this.fullName = fullName;
|
||||||
|
this.keyID = keyID;
|
||||||
|
this.avatar = avatar;
|
||||||
|
this.defaultType = defaultType;
|
||||||
|
this.lastUsed = lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addSite(final MPSite site) {
|
||||||
|
sites.add( site );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFullName() {
|
||||||
|
return fullName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasKeyID(final byte[] keyID) {
|
||||||
|
return Arrays.equals( this.keyID, keyID );
|
||||||
|
}
|
||||||
|
|
||||||
|
public String exportKeyID() {
|
||||||
|
return CodeUtils.encodeHex( keyID );
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAvatar() {
|
||||||
|
return avatar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MPSiteType getDefaultType() {
|
||||||
|
return defaultType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DateTime getLastUsed() {
|
||||||
|
return lastUsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Iterable<MPSite> getSites() {
|
||||||
|
return sites;
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>masterpassword-algorithm</module>
|
<module>masterpassword-algorithm</module>
|
||||||
|
<module>masterpassword-model</module>
|
||||||
<module>masterpassword-cli</module>
|
<module>masterpassword-cli</module>
|
||||||
<module>masterpassword-gui</module>
|
<module>masterpassword-gui</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
Loading…
Reference in New Issue
Block a user