Fixed counter type, added Android integrity testing.
[IMPROVED] Type of Master Password counter is unsigned int; Guava's UnsignedInteger allows us to better enforce that in code, fixing a few places where ints were treated badly, limiting counter support. [ADDED] An integrity test activity to the Android app to ensure the Android device properly generates the expected passwords before allowing the user to rely on it. [UPDATED] Made standard test suite available for all without needing JAXB; implemented SAX reading of mpw_tests.xml + a good API for running the tests and getting feedback at runtime.
This commit is contained in:
parent
188353d39b
commit
f782b2ef62
@ -23,7 +23,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||||
<artifactId>opal-system</artifactId>
|
<artifactId>opal-system</artifactId>
|
||||||
<version>1.6-p8</version>
|
<version>1.6-p9</version>
|
||||||
<exclusions>
|
<exclusions>
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>joda-time</groupId>
|
<groupId>joda-time</groupId>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.*;
|
import com.lyndir.lhunath.opal.system.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -74,7 +75,7 @@ public abstract class MasterKey {
|
|||||||
return idForBytes( getKey() );
|
return idForBytes( getKey() );
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract String encode(@Nonnull final String siteName, final MPSiteType siteType, int siteCounter,
|
public abstract String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull final UnsignedInteger siteCounter,
|
||||||
final MPSiteVariant siteVariant, @Nullable final String siteContext);
|
final MPSiteVariant siteVariant, @Nullable final String siteContext);
|
||||||
|
|
||||||
public boolean isValid() {
|
public boolean isValid() {
|
||||||
@ -106,7 +107,9 @@ public abstract class MasterKey {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract byte[] bytesForInt(final int integer);
|
protected abstract byte[] bytesForInt(final int number);
|
||||||
|
|
||||||
|
protected abstract byte[] bytesForInt(@Nonnull final UnsignedInteger number);
|
||||||
|
|
||||||
protected abstract byte[] idForBytes(final byte[] bytes);
|
protected abstract byte[] idForBytes(final byte[] bytes);
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package com.lyndir.masterpassword;
|
|||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lambdaworks.crypto.SCrypt;
|
import com.lambdaworks.crypto.SCrypt;
|
||||||
import com.lyndir.lhunath.opal.system.*;
|
import com.lyndir.lhunath.opal.system.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
@ -10,14 +11,15 @@ import java.nio.*;
|
|||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bugs:
|
* bugs:
|
||||||
* - does math with chars whose signedness was platform-dependent.
|
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
||||||
* - miscounted the byte-length fromInt multi-byte site names.
|
* - V1: miscounted the byte-length fromInt multi-byte site names.
|
||||||
* - miscounted the byte-length fromInt multi-byte full names.
|
* - V0: does math with chars whose signedness was platform-dependent.
|
||||||
*
|
*
|
||||||
* @author lhunath, 2014-08-30
|
* @author lhunath, 2014-08-30
|
||||||
*/
|
*/
|
||||||
@ -75,18 +77,19 @@ public class MasterKeyV0 extends MasterKey {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
|
@Override
|
||||||
@Nullable final String siteContext) {
|
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
||||||
|
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
logger.trc( "siteName: %s", siteName );
|
||||||
logger.trc( "siteCounter: %d", siteCounter );
|
logger.trc( "siteCounter: %d", siteCounter.longValue() );
|
||||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
||||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||||
|
|
||||||
if (siteCounter == 0)
|
if (siteCounter.longValue() == 0)
|
||||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (300 * 1000)) * 300 );
|
||||||
|
|
||||||
String siteScope = siteVariant.getScope();
|
String siteScope = siteVariant.getScope();
|
||||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||||
@ -135,8 +138,13 @@ public class MasterKeyV0 extends MasterKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected byte[] bytesForInt(final int integer) {
|
protected byte[] bytesForInt(final int number) {
|
||||||
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
|
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( number ).array();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected byte[] bytesForInt(@Nonnull final UnsignedInteger number) {
|
||||||
|
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( number.intValue() ).array();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,15 +2,17 @@ package com.lyndir.masterpassword;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.*;
|
import com.lyndir.lhunath.opal.system.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bugs:
|
* bugs:
|
||||||
* - miscounted the byte-length fromInt multi-byte site names.
|
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
||||||
* - miscounted the byte-length fromInt multi-byte full names.
|
* - V1: miscounted the byte-length fromInt multi-byte site names.
|
||||||
*
|
*
|
||||||
* @author lhunath, 2014-08-30
|
* @author lhunath, 2014-08-30
|
||||||
*/
|
*/
|
||||||
@ -29,18 +31,19 @@ public class MasterKeyV1 extends MasterKeyV0 {
|
|||||||
return Version.V1;
|
return Version.V1;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
|
@Override
|
||||||
@Nullable final String siteContext) {
|
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
||||||
|
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
logger.trc( "siteName: %s", siteName );
|
||||||
logger.trc( "siteCounter: %d", siteCounter );
|
logger.trc( "siteCounter: %d", siteCounter.longValue() );
|
||||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
||||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||||
|
|
||||||
if (siteCounter == 0)
|
if (siteCounter.longValue() == 0)
|
||||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (300 * 1000)) * 300 );
|
||||||
|
|
||||||
String siteScope = siteVariant.getScope();
|
String siteScope = siteVariant.getScope();
|
||||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||||
|
@ -2,14 +2,16 @@ package com.lyndir.masterpassword;
|
|||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* bugs:
|
* bugs:
|
||||||
* - miscounted the byte-length fromInt multi-byte full names.
|
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
||||||
*
|
*
|
||||||
* @author lhunath, 2014-08-30
|
* @author lhunath, 2014-08-30
|
||||||
*/
|
*/
|
||||||
@ -28,18 +30,19 @@ public class MasterKeyV2 extends MasterKeyV1 {
|
|||||||
return Version.V2;
|
return Version.V2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
|
@Override
|
||||||
@Nullable final String siteContext) {
|
public String encode(@Nonnull final String siteName, final MPSiteType siteType, @Nonnull UnsignedInteger siteCounter,
|
||||||
|
final MPSiteVariant siteVariant, @Nullable final String siteContext) {
|
||||||
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
Preconditions.checkArgument( siteType.getTypeClass() == MPSiteTypeClass.Generated );
|
||||||
Preconditions.checkArgument( !siteName.isEmpty() );
|
Preconditions.checkArgument( !siteName.isEmpty() );
|
||||||
|
|
||||||
logger.trc( "siteName: %s", siteName );
|
logger.trc( "siteName: %s", siteName );
|
||||||
logger.trc( "siteCounter: %d", siteCounter );
|
logger.trc( "siteCounter: %d", siteCounter.longValue() );
|
||||||
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
logger.trc( "siteVariant: %d (%s)", siteVariant.ordinal(), siteVariant );
|
||||||
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
logger.trc( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||||
|
|
||||||
if (siteCounter == 0)
|
if (siteCounter.longValue() == 0)
|
||||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (300 * 1000)) * 300 );
|
||||||
|
|
||||||
String siteScope = siteVariant.getScope();
|
String siteScope = siteVariant.getScope();
|
||||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||||
|
@ -12,13 +12,13 @@
|
|||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:allowBackup="true">
|
android:allowBackup="true">
|
||||||
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
|
<activity android:name=".TestActivity" android:theme="@style/MPTheme">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".UsersActivity" />
|
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -107,6 +107,13 @@
|
|||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lyndir.masterpassword</groupId>
|
||||||
|
<artifactId>masterpassword-tests</artifactId>
|
||||||
|
<version>GIT-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- EXTERNAL DEPENDENCIES -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.jakewharton</groupId>
|
<groupId>com.jakewharton</groupId>
|
||||||
<artifactId>butterknife</artifactId>
|
<artifactId>butterknife</artifactId>
|
||||||
|
@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:fillViewport="true"
|
||||||
|
android:background="@drawable/background">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="20dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="1dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1" />
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_margin="8dp"
|
||||||
|
style="@android:style/Widget.ProgressBar.Horizontal" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/statusView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@android:color/secondary_text_dark"
|
||||||
|
android:text="@string/tests_testing" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/logView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:gravity="bottom"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:textSize="9sp"
|
||||||
|
android:textColor="@android:color/tertiary_text_dark" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/actionButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text="@string/tests_btn_testing"
|
||||||
|
android:onClick="onAction"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
@ -11,4 +11,12 @@
|
|||||||
<string name="siteCounter_hint">Password #</string>
|
<string name="siteCounter_hint">Password #</string>
|
||||||
<string name="siteVersion_hint">Algorithm</string>
|
<string name="siteVersion_hint">Algorithm</string>
|
||||||
<string name="empty" />
|
<string name="empty" />
|
||||||
|
<string name="tests_unavailable">Test suite unavailable.</string>
|
||||||
|
<string name="tests_btn_unavailable">Exit</string>
|
||||||
|
<string name="tests_testing">Testing device\'s password generation integrity…</string>
|
||||||
|
<string name="tests_btn_testing">Please Stand By…</string>
|
||||||
|
<string name="tests_failed">Incompatible device or OS.</string>
|
||||||
|
<string name="tests_btn_failed">Exit</string>
|
||||||
|
<string name="tests_passed">Integrity checks passed!</string>
|
||||||
|
<string name="tests_btn_passed">Continue</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
import android.app.*;
|
import android.app.*;
|
||||||
@ -16,9 +17,9 @@ import android.widget.*;
|
|||||||
import butterknife.ButterKnife;
|
import butterknife.ButterKnife;
|
||||||
import butterknife.InjectView;
|
import butterknife.InjectView;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.*;
|
import java.util.concurrent.*;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -87,6 +88,10 @@ public class EmergencyActivity extends Activity {
|
|||||||
private int hc_masterPassword;
|
private int hc_masterPassword;
|
||||||
private String sitePassword;
|
private String sitePassword;
|
||||||
|
|
||||||
|
public static void start(Context context) {
|
||||||
|
context.startActivity( new Intent( context, EmergencyActivity.class ) );
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate( savedInstanceState );
|
super.onCreate( savedInstanceState );
|
||||||
@ -261,7 +266,7 @@ public class EmergencyActivity extends Activity {
|
|||||||
private void updateSitePassword() {
|
private void updateSitePassword() {
|
||||||
final String siteName = siteNameField.getText().toString();
|
final String siteName = siteNameField.getText().toString();
|
||||||
final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
|
final MPSiteType type = (MPSiteType) siteTypeField.getSelectedItem();
|
||||||
final int counter = ConversionUtils.toIntegerNN( counterField.getText() );
|
final UnsignedInteger counter = UnsignedInteger.valueOf( ifNotNullElse( counterField.getText(), "1" ).toString() );
|
||||||
|
|
||||||
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
if (masterKeyFuture == null || siteName.isEmpty() || type == null) {
|
||||||
sitePasswordField.setText( "" );
|
sitePasswordField.setText( "" );
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lhunath, 2015-12-22
|
||||||
|
*/
|
||||||
|
public class MainThreadExecutor extends AbstractExecutorService {
|
||||||
|
|
||||||
|
private final Handler mHandler = new Handler( Looper.getMainLooper() );
|
||||||
|
private final Set<Runnable> commands = Sets.newLinkedHashSet();
|
||||||
|
private boolean shutdown;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(final Runnable command) {
|
||||||
|
if (shutdown)
|
||||||
|
throw new RejectedExecutionException( "This executor has been shut down" );
|
||||||
|
|
||||||
|
synchronized (commands) {
|
||||||
|
commands.add( command );
|
||||||
|
|
||||||
|
mHandler.post( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
synchronized (commands) {
|
||||||
|
if (!commands.remove( command ))
|
||||||
|
// Command was removed, not executing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
command.run();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void shutdown() {
|
||||||
|
shutdown = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<Runnable> shutdownNow() {
|
||||||
|
shutdown = true;
|
||||||
|
mHandler.removeCallbacksAndMessages( null );
|
||||||
|
|
||||||
|
synchronized (commands) {
|
||||||
|
ImmutableList<Runnable> pendingTasks = ImmutableList.copyOf( commands );
|
||||||
|
commands.clear();
|
||||||
|
commands.notify();
|
||||||
|
return pendingTasks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isShutdown() {
|
||||||
|
return shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isTerminated() {
|
||||||
|
synchronized (commands) {
|
||||||
|
return shutdown && commands.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean awaitTermination(final long timeout, final TimeUnit unit)
|
||||||
|
throws InterruptedException {
|
||||||
|
if (isTerminated())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
commands.wait( unit.toMillis( timeout ) );
|
||||||
|
return isTerminated();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,171 @@
|
|||||||
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
|
import android.app.*;
|
||||||
|
import android.os.*;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.*;
|
||||||
|
import butterknife.ButterKnife;
|
||||||
|
import butterknife.InjectView;
|
||||||
|
import com.google.common.base.*;
|
||||||
|
import com.google.common.collect.*;
|
||||||
|
import com.google.common.util.concurrent.*;
|
||||||
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
|
||||||
|
public class TestActivity extends Activity implements MPTestSuite.Listener {
|
||||||
|
|
||||||
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
|
private static final Logger logger = Logger.get( TestActivity.class );
|
||||||
|
|
||||||
|
private final ListeningExecutorService backgroundExecutor = MoreExecutors.listeningDecorator( Executors.newSingleThreadExecutor() );
|
||||||
|
private final ListeningExecutorService mainExecutor = MoreExecutors.listeningDecorator( new MainThreadExecutor() );
|
||||||
|
|
||||||
|
@InjectView(R.id.progressView)
|
||||||
|
ProgressBar progressView;
|
||||||
|
|
||||||
|
@InjectView(R.id.statusView)
|
||||||
|
TextView statusView;
|
||||||
|
|
||||||
|
@InjectView(R.id.logView)
|
||||||
|
TextView logView;
|
||||||
|
|
||||||
|
@InjectView(R.id.actionButton)
|
||||||
|
Button actionButton;
|
||||||
|
|
||||||
|
private MPTestSuite testSuite;
|
||||||
|
private ListenableFuture<Boolean> testFuture;
|
||||||
|
private Runnable action;
|
||||||
|
private ImmutableSet<String> testNames;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate( savedInstanceState );
|
||||||
|
Res.init( getResources() );
|
||||||
|
|
||||||
|
getWindow().setFlags( WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE );
|
||||||
|
setContentView( R.layout.activity_test );
|
||||||
|
ButterKnife.inject( this );
|
||||||
|
|
||||||
|
try {
|
||||||
|
setStatus( 0, 0, null );
|
||||||
|
testSuite = new MPTestSuite();
|
||||||
|
testSuite.setListener( this );
|
||||||
|
testNames = FluentIterable.from( testSuite.getTests().getCases() ).transform(
|
||||||
|
new Function<MPTests.Case, String>() {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String apply(@Nullable final MPTests.Case input) {
|
||||||
|
return input == null? null: input.identifier;
|
||||||
|
}
|
||||||
|
} ).filter( Predicates.notNull() ).toSet();
|
||||||
|
}
|
||||||
|
catch (MPTestSuite.UnavailableException e) {
|
||||||
|
logger.err( e, "While loading test suite" );
|
||||||
|
setStatus( R.string.tests_unavailable, R.string.tests_btn_unavailable, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
|
||||||
|
final Set<String> integrityTestsPassed = getPreferences( MODE_PRIVATE ).getStringSet( "integrityTestsPassed",
|
||||||
|
ImmutableSet.<String>of() );
|
||||||
|
if (!FluentIterable.from( testNames ).anyMatch( new Predicate<String>() {
|
||||||
|
@Override
|
||||||
|
public boolean apply(@Nullable final String testName) {
|
||||||
|
return !integrityTestsPassed.contains( testName );
|
||||||
|
}
|
||||||
|
} )) {
|
||||||
|
// None of the tests we need to perform were missing from the tests that have already been passed on this device.
|
||||||
|
finish();
|
||||||
|
EmergencyActivity.start( TestActivity.this );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
|
||||||
|
if (testFuture == null) {
|
||||||
|
setStatus( R.string.tests_testing, R.string.tests_btn_testing, null );
|
||||||
|
Futures.addCallback( testFuture = backgroundExecutor.submit( testSuite ), new FutureCallback<Boolean>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(@Nullable final Boolean result) {
|
||||||
|
if (result != null && result)
|
||||||
|
setStatus( R.string.tests_passed, R.string.tests_btn_passed, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
getPreferences( MODE_PRIVATE ).edit().putStringSet( "integrityTestsPassed", testNames ).apply();
|
||||||
|
finish();
|
||||||
|
EmergencyActivity.start( TestActivity.this );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
else
|
||||||
|
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(final Throwable t) {
|
||||||
|
logger.err( t, "While running test suite" );
|
||||||
|
setStatus( R.string.tests_failed, R.string.tests_btn_failed, new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}, mainExecutor );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAction(View v) {
|
||||||
|
if (action != null)
|
||||||
|
action.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setStatus(int statusId, int buttonId, @Nullable Runnable action) {
|
||||||
|
this.action = action;
|
||||||
|
|
||||||
|
if (statusId == 0)
|
||||||
|
statusView.setText( null );
|
||||||
|
else
|
||||||
|
statusView.setText( statusId );
|
||||||
|
|
||||||
|
if (buttonId == 0)
|
||||||
|
actionButton.setText( null );
|
||||||
|
else
|
||||||
|
actionButton.setText( buttonId );
|
||||||
|
actionButton.setEnabled( action != null );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void progress(final int current, final int max, final String messageFormat, final Object... args) {
|
||||||
|
runOnUiThread( new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
logView.append( strf( '\n' + messageFormat, args ) );
|
||||||
|
|
||||||
|
progressView.setMax( max );
|
||||||
|
progressView.setProgress( current );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,7 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.ifNotNullElse;
|
||||||
@ -23,6 +22,7 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
|||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
import com.google.common.io.LineReader;
|
import com.google.common.io.LineReader;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||||
import com.lyndir.lhunath.opal.system.util.StringUtils;
|
import com.lyndir.lhunath.opal.system.util.StringUtils;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -52,7 +52,7 @@ public class CLI {
|
|||||||
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
|
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
|
||||||
MPSiteVariant variant = MPSiteVariant.Password;
|
MPSiteVariant variant = MPSiteVariant.Password;
|
||||||
String siteCounterName = ifNotNullElse( System.getenv( ENV_SITECOUNTER ), "" );
|
String siteCounterName = ifNotNullElse( System.getenv( ENV_SITECOUNTER ), "" );
|
||||||
int siteCounter = siteCounterName.isEmpty()? 1: Integer.parseInt( siteCounterName );
|
UnsignedInteger siteCounter = siteCounterName.isEmpty()? UnsignedInteger.valueOf( 1 ): UnsignedInteger.valueOf( siteCounterName );
|
||||||
|
|
||||||
// Parse information from option arguments.
|
// Parse information from option arguments.
|
||||||
boolean userNameArg = false, typeArg = false, counterArg = false, variantArg = false, contextArg = false;
|
boolean userNameArg = false, typeArg = false, counterArg = false, variantArg = false, contextArg = false;
|
||||||
@ -77,7 +77,7 @@ public class CLI {
|
|||||||
else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
else if ("-c".equals( arg ) || "--counter".equals( arg ))
|
||||||
counterArg = true;
|
counterArg = true;
|
||||||
else if (counterArg) {
|
else if (counterArg) {
|
||||||
siteCounter = ConversionUtils.toIntegerNN( arg );
|
siteCounter = UnsignedInteger.valueOf( arg );
|
||||||
counterArg = false;
|
counterArg = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
import com.lyndir.masterpassword.MasterKey;
|
||||||
|
|
||||||
@ -11,10 +12,10 @@ public class IncognitoSite extends Site {
|
|||||||
|
|
||||||
private String siteName;
|
private String siteName;
|
||||||
private MPSiteType siteType;
|
private MPSiteType siteType;
|
||||||
private int siteCounter;
|
private UnsignedInteger siteCounter;
|
||||||
private MasterKey.Version algorithmVersion;
|
private MasterKey.Version algorithmVersion;
|
||||||
|
|
||||||
public IncognitoSite(final String siteName, final MPSiteType siteType, final int siteCounter,
|
public IncognitoSite(final String siteName, final MPSiteType siteType, final UnsignedInteger siteCounter,
|
||||||
final MasterKey.Version algorithmVersion) {
|
final MasterKey.Version algorithmVersion) {
|
||||||
this.siteName = siteName;
|
this.siteName = siteName;
|
||||||
this.siteType = siteType;
|
this.siteType = siteType;
|
||||||
@ -48,11 +49,11 @@ public class IncognitoSite extends Site {
|
|||||||
this.algorithmVersion = algorithmVersion;
|
this.algorithmVersion = algorithmVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSiteCounter() {
|
public UnsignedInteger getSiteCounter() {
|
||||||
return siteCounter;
|
return siteCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSiteCounter(final int siteCounter) {
|
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||||
this.siteCounter = siteCounter;
|
this.siteCounter = siteCounter;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.lyndir.masterpassword.gui;
|
package com.lyndir.masterpassword.gui;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
import com.lyndir.masterpassword.MasterKey;
|
||||||
import com.lyndir.masterpassword.model.*;
|
import com.lyndir.masterpassword.model.*;
|
||||||
@ -55,13 +56,13 @@ public class ModelSite extends Site {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSiteCounter() {
|
public UnsignedInteger getSiteCounter() {
|
||||||
return model.getSiteCounter();
|
return model.getSiteCounter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSiteCounter(final int siteCounter) {
|
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||||
if (siteCounter != getSiteCounter()) {
|
if (siteCounter.equals( getSiteCounter() )) {
|
||||||
model.setSiteCounter( siteCounter );
|
model.setSiteCounter( siteCounter );
|
||||||
MPUserFileManager.get().save();
|
MPUserFileManager.get().save();
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
|
|||||||
|
|
||||||
import com.google.common.collect.FluentIterable;
|
import com.google.common.collect.FluentIterable;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.google.common.util.concurrent.*;
|
import com.google.common.util.concurrent.*;
|
||||||
import com.lyndir.lhunath.opal.system.util.PredicateNN;
|
import com.lyndir.lhunath.opal.system.util.PredicateNN;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
@ -118,7 +119,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
|
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
|
||||||
Components.stud(), //
|
Components.stud(), //
|
||||||
siteCounterField = Components.spinner(
|
siteCounterField = Components.spinner(
|
||||||
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) );
|
new SpinnerNumberModel( 1L, 1L, UnsignedInteger.MAX_VALUE, 1L ) ) );
|
||||||
sitePanel.add( siteSettings );
|
sitePanel.add( siteSettings );
|
||||||
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
|
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||||
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
||||||
@ -174,7 +175,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
// Tip
|
// Tip
|
||||||
tipLabel = Components.label( " ", SwingConstants.CENTER );
|
tipLabel = Components.label( " ", SwingConstants.CENTER );
|
||||||
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
tipLabel.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||||
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField, Box.createGlue(), tipLabel );
|
JPanel passwordContainer = Components.boxLayout( BoxLayout.PAGE_AXIS, maskPasswordField, Box.createGlue(), passwordField,
|
||||||
|
Box.createGlue(), tipLabel );
|
||||||
passwordContainer.setOpaque( true );
|
passwordContainer.setOpaque( true );
|
||||||
passwordContainer.setBackground( Color.white );
|
passwordContainer.setBackground( Color.white );
|
||||||
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
|
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
|
||||||
@ -210,7 +212,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
|
|
||||||
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
|
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
|
||||||
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
|
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
|
||||||
final int siteCounter = (Integer) siteCounterField.getValue();
|
final UnsignedInteger siteCounter = UnsignedInteger.valueOf( ((Number) siteCounterField.getValue()).longValue() );
|
||||||
|
|
||||||
Iterable<Site> siteResults = user.findSitesByName( siteNameQuery );
|
Iterable<Site> siteResults = user.findSitesByName( siteNameQuery );
|
||||||
if (!allowNameCompletion)
|
if (!allowNameCompletion)
|
||||||
@ -222,7 +224,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
|||||||
} );
|
} );
|
||||||
final Site site = Iterables.getFirst( siteResults,
|
final Site site = Iterables.getFirst( siteResults,
|
||||||
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
|
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
|
||||||
if (currentSite != null && site.getSiteName().equals(currentSite.getSiteName())) {
|
if (currentSite != null && currentSite.getSiteName().equals( site.getSiteName() )) {
|
||||||
site.setSiteType( siteType );
|
site.setSiteType( siteType );
|
||||||
site.setAlgorithmVersion( siteVersion );
|
site.setAlgorithmVersion( siteVersion );
|
||||||
site.setSiteCounter( siteCounter );
|
site.setSiteCounter( siteCounter );
|
||||||
|
@ -2,6 +2,7 @@ package com.lyndir.masterpassword.gui;
|
|||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.masterpassword.MPSiteType;
|
import com.lyndir.masterpassword.MPSiteType;
|
||||||
import com.lyndir.masterpassword.MasterKey;
|
import com.lyndir.masterpassword.MasterKey;
|
||||||
|
|
||||||
@ -23,9 +24,9 @@ public abstract class Site {
|
|||||||
|
|
||||||
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
|
public abstract void setAlgorithmVersion(final MasterKey.Version algorithmVersion);
|
||||||
|
|
||||||
public abstract int getSiteCounter();
|
public abstract UnsignedInteger getSiteCounter();
|
||||||
|
|
||||||
public abstract void setSiteCounter(final int siteCounter);
|
public abstract void setSiteCounter(final UnsignedInteger siteCounter);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
|
@ -26,6 +26,11 @@
|
|||||||
<version>GIT-SNAPSHOT</version>
|
<version>GIT-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- EXTERNAL DEPENDENCIES -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>joda-time</groupId>
|
||||||
|
<artifactId>joda-time</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.auto.value</groupId>
|
<groupId>com.google.auto.value</groupId>
|
||||||
<artifactId>auto-value</artifactId>
|
<artifactId>auto-value</artifactId>
|
||||||
|
@ -2,6 +2,7 @@ package com.lyndir.masterpassword.model;
|
|||||||
|
|
||||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.masterpassword.*;
|
import com.lyndir.masterpassword.*;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
@ -14,14 +15,14 @@ import org.joda.time.Instant;
|
|||||||
public class MPSite {
|
public class MPSite {
|
||||||
|
|
||||||
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong;
|
public static final MPSiteType DEFAULT_TYPE = MPSiteType.GeneratedLong;
|
||||||
public static final int DEFAULT_COUNTER = 1;
|
public static final UnsignedInteger DEFAULT_COUNTER = UnsignedInteger.valueOf( 1 );
|
||||||
|
|
||||||
private final MPUser user;
|
private final MPUser user;
|
||||||
private MasterKey.Version algorithmVersion;
|
private MasterKey.Version algorithmVersion;
|
||||||
private Instant lastUsed;
|
private Instant lastUsed;
|
||||||
private String siteName;
|
private String siteName;
|
||||||
private MPSiteType siteType;
|
private MPSiteType siteType;
|
||||||
private int siteCounter;
|
private UnsignedInteger siteCounter;
|
||||||
private int uses;
|
private int uses;
|
||||||
private String loginName;
|
private String loginName;
|
||||||
|
|
||||||
@ -29,7 +30,7 @@ public class MPSite {
|
|||||||
this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
|
this( user, siteName, DEFAULT_TYPE, DEFAULT_COUNTER );
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final int siteCounter) {
|
public MPSite(final MPUser user, final String siteName, final MPSiteType siteType, final UnsignedInteger siteCounter) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.algorithmVersion = MasterKey.Version.CURRENT;
|
this.algorithmVersion = MasterKey.Version.CURRENT;
|
||||||
this.lastUsed = new Instant();
|
this.lastUsed = new Instant();
|
||||||
@ -39,7 +40,7 @@ public class MPSite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName,
|
protected MPSite(final MPUser user, final MasterKey.Version algorithmVersion, final Instant lastUsed, final String siteName,
|
||||||
final MPSiteType siteType, final int siteCounter, final int uses, @Nullable final String loginName,
|
final MPSiteType siteType, final UnsignedInteger siteCounter, final int uses, @Nullable final String loginName,
|
||||||
@Nullable final String importContent) {
|
@Nullable final String importContent) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.algorithmVersion = algorithmVersion;
|
this.algorithmVersion = algorithmVersion;
|
||||||
@ -101,11 +102,11 @@ public class MPSite {
|
|||||||
this.siteType = siteType;
|
this.siteType = siteType;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSiteCounter() {
|
public UnsignedInteger getSiteCounter() {
|
||||||
return siteCounter;
|
return siteCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSiteCounter(final int siteCounter) {
|
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||||
this.siteCounter = siteCounter;
|
this.siteCounter = siteCounter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.io.CharStreams;
|
import com.google.common.io.CharStreams;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.CodeUtils;
|
import com.lyndir.lhunath.opal.system.CodeUtils;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||||
@ -139,7 +140,7 @@ public class MPSiteUnmarshaller {
|
|||||||
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
|
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
|
||||||
siteMatcher.group( 7 ), //
|
siteMatcher.group( 7 ), //
|
||||||
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||||
ConversionUtils.toIntegerNN( siteMatcher.group( 5 ).replace( ":", "" ) ), //
|
UnsignedInteger.valueOf( siteMatcher.group( 5 ).replace( ":", "" ) ), //
|
||||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
||||||
siteMatcher.group( 6 ), //
|
siteMatcher.group( 6 ), //
|
||||||
siteMatcher.group( 8 ) );
|
siteMatcher.group( 8 ) );
|
||||||
|
@ -1,24 +1,33 @@
|
|||||||
package com.lyndir.masterpassword;
|
package com.lyndir.masterpassword;
|
||||||
|
|
||||||
import com.google.common.io.Resources;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
|
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||||
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
|
import com.lyndir.lhunath.opal.system.util.NNFunctionNN;
|
||||||
import java.net.URL;
|
import java.io.IOException;
|
||||||
|
import java.util.Deque;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Callable;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.xml.bind.JAXBContext;
|
import javax.xml.parsers.*;
|
||||||
import javax.xml.bind.JAXBException;
|
import org.xml.sax.Attributes;
|
||||||
|
import org.xml.sax.SAXException;
|
||||||
|
import org.xml.sax.ext.DefaultHandler2;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 2015-12-22
|
* @author lhunath, 2015-12-22
|
||||||
*/
|
*/
|
||||||
public class MPTestSuite {
|
public class MPTestSuite implements Callable<Boolean> {
|
||||||
|
|
||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( MPTestSuite.class );
|
private static final Logger logger = Logger.get( MPTestSuite.class );
|
||||||
private static final String DEFAULT_RESOURCE_NAME = "mpw_tests.xml";
|
private static final String DEFAULT_RESOURCE_NAME = "mpw_tests.xml";
|
||||||
|
|
||||||
private MPTests tests;
|
private MPTests tests;
|
||||||
|
private Listener listener;
|
||||||
|
|
||||||
public MPTestSuite()
|
public MPTestSuite()
|
||||||
throws UnavailableException {
|
throws UnavailableException {
|
||||||
@ -28,15 +37,78 @@ public class MPTestSuite {
|
|||||||
public MPTestSuite(String resourceName)
|
public MPTestSuite(String resourceName)
|
||||||
throws UnavailableException {
|
throws UnavailableException {
|
||||||
try {
|
try {
|
||||||
URL testCasesResource = Resources.getResource( resourceName );
|
tests = new MPTests();
|
||||||
tests = (MPTests) JAXBContext.newInstance( MPTests.class ).createUnmarshaller().unmarshal( testCasesResource );
|
tests.cases = Lists.newLinkedList();
|
||||||
|
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
|
||||||
|
parser.parse( Thread.currentThread().getContextClassLoader().getResourceAsStream( resourceName ), new DefaultHandler2() {
|
||||||
|
private Deque<String> currentTags = Lists.newLinkedList();
|
||||||
|
private Deque<StringBuilder> currentTexts = Lists.newLinkedList();
|
||||||
|
private MPTests.Case currentCase;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startElement(final String uri, final String localName, final String qName, final Attributes attributes)
|
||||||
|
throws SAXException {
|
||||||
|
super.startElement( uri, localName, qName, attributes );
|
||||||
|
currentTags.push( qName );
|
||||||
|
currentTexts.push( new StringBuilder() );
|
||||||
|
|
||||||
|
if ("case".equals( qName )) {
|
||||||
|
currentCase = new MPTests.Case();
|
||||||
|
currentCase.identifier = attributes.getValue( "id" );
|
||||||
|
currentCase.parent = attributes.getValue( "parent" );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endElement(final String uri, final String localName, final String qName)
|
||||||
|
throws SAXException {
|
||||||
|
super.endElement( uri, localName, qName );
|
||||||
|
Preconditions.checkState( qName.equals( currentTags.pop() ) );
|
||||||
|
String text = currentTexts.pop().toString();
|
||||||
|
|
||||||
|
if ("case".equals( qName ))
|
||||||
|
tests.cases.add( currentCase );
|
||||||
|
if ("algorithm".equals( qName ))
|
||||||
|
currentCase.algorithm = ConversionUtils.toInteger( text ).orNull();
|
||||||
|
if ("fullName".equals( qName ))
|
||||||
|
currentCase.fullName = text;
|
||||||
|
if ("masterPassword".equals( qName ))
|
||||||
|
currentCase.masterPassword = text;
|
||||||
|
if ("keyID".equals( qName ))
|
||||||
|
currentCase.keyID = text;
|
||||||
|
if ("siteName".equals( qName ))
|
||||||
|
currentCase.siteName = text;
|
||||||
|
if ("siteCounter".equals( qName ))
|
||||||
|
currentCase.siteCounter = text.isEmpty()? null: UnsignedInteger.valueOf( text );
|
||||||
|
if ("siteType".equals( qName ))
|
||||||
|
currentCase.siteType = text;
|
||||||
|
if ("siteVariant".equals( qName ))
|
||||||
|
currentCase.siteVariant = text;
|
||||||
|
if ("siteContext".equals( qName ))
|
||||||
|
currentCase.siteContext = text;
|
||||||
|
if ("result".equals( qName ))
|
||||||
|
currentCase.result = text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void characters(final char[] ch, final int start, final int length)
|
||||||
|
throws SAXException {
|
||||||
|
super.characters( ch, start, length );
|
||||||
|
|
||||||
|
currentTexts.peek().append( ch, start, length );
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
catch (IllegalArgumentException | ParserConfigurationException | SAXException | IOException e) {
|
||||||
|
throw new UnavailableException( e );
|
||||||
|
}
|
||||||
|
|
||||||
for (MPTests.Case testCase : tests.getCases())
|
for (MPTests.Case testCase : tests.getCases())
|
||||||
testCase.initializeParentHierarchy( tests );
|
testCase.initializeParentHierarchy( tests );
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException | JAXBException e) {
|
|
||||||
throw new UnavailableException( e );
|
public void setListener(final Listener listener) {
|
||||||
}
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MPTests getTests() {
|
public MPTests getTests() {
|
||||||
@ -44,22 +116,39 @@ public class MPTestSuite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean forEach(String testName, NNFunctionNN<MPTests.Case, Boolean> testFunction) {
|
public boolean forEach(String testName, NNFunctionNN<MPTests.Case, Boolean> testFunction) {
|
||||||
for (MPTests.Case testCase : tests.getCases()) {
|
List<MPTests.Case> cases = tests.getCases();
|
||||||
|
for (int c = 0; c < cases.size(); c++) {
|
||||||
|
MPTests.Case testCase = cases.get( c );
|
||||||
if (testCase.getResult().isEmpty())
|
if (testCase.getResult().isEmpty())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
logger.inf( "[%s] on %s...", testName, testCase.getIdentifier() );
|
progress( Logger.Target.INFO, c, cases.size(), //
|
||||||
|
"[%s] on %s...", testName, testCase.getIdentifier() );
|
||||||
|
|
||||||
if (!testFunction.apply( testCase )) {
|
if (!testFunction.apply( testCase )) {
|
||||||
logger.err( "[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
|
progress( Logger.Target.ERROR, cases.size(), cases.size(), //
|
||||||
|
"[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
logger.inf( "[%s] on %s: passed!", testName, testCase.getIdentifier() );
|
|
||||||
|
progress( Logger.Target.INFO, c + 1, cases.size(), //
|
||||||
|
"[%s] on %s: passed!", testName, testCase.getIdentifier() );
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean run() {
|
private void progress(final Logger.Target target, final int current, final int max, final String format, final Object... args) {
|
||||||
|
logger.log( target, format, args );
|
||||||
|
|
||||||
|
if (listener != null)
|
||||||
|
listener.progress( current, max, format, args );
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Boolean call()
|
||||||
|
throws Exception {
|
||||||
return forEach( "mpw", new NNFunctionNN<MPTests.Case, Boolean>() {
|
return forEach( "mpw", new NNFunctionNN<MPTests.Case, Boolean>() {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
@ -79,4 +168,10 @@ public class MPTestSuite {
|
|||||||
super( cause );
|
super( cause );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface Listener {
|
||||||
|
|
||||||
|
void progress(int current, int max, String messageFormat, Object... args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,18 +3,17 @@ package com.lyndir.masterpassword;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
|
||||||
|
|
||||||
|
import com.google.common.primitives.UnsignedInteger;
|
||||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||||
import com.lyndir.lhunath.opal.system.util.*;
|
import com.lyndir.lhunath.opal.system.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
import javax.xml.bind.annotation.*;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author lhunath, 14-12-05
|
* @author lhunath, 14-12-05
|
||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "tests")
|
|
||||||
public class MPTests {
|
public class MPTests {
|
||||||
|
|
||||||
private static final String ID_DEFAULT = "default";
|
private static final String ID_DEFAULT = "default";
|
||||||
@ -22,8 +21,7 @@ public class MPTests {
|
|||||||
@SuppressWarnings("UnusedDeclaration")
|
@SuppressWarnings("UnusedDeclaration")
|
||||||
private static final Logger logger = Logger.get( MPTests.class );
|
private static final Logger logger = Logger.get( MPTests.class );
|
||||||
|
|
||||||
@XmlElement(name = "case")
|
List<Case> cases;
|
||||||
private List<Case> cases;
|
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public List<Case> getCases() {
|
public List<Case> getCases() {
|
||||||
@ -47,33 +45,20 @@ public class MPTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@XmlRootElement(name = "case")
|
|
||||||
public static class Case {
|
public static class Case {
|
||||||
|
|
||||||
@XmlAttribute(name = "id")
|
String identifier;
|
||||||
private String identifier;
|
String parent;
|
||||||
@XmlAttribute
|
Integer algorithm;
|
||||||
private String parent;
|
String fullName;
|
||||||
@XmlElement
|
String masterPassword;
|
||||||
private String algorithm;
|
String keyID;
|
||||||
@XmlElement
|
String siteName;
|
||||||
private String fullName;
|
UnsignedInteger siteCounter;
|
||||||
@XmlElement
|
String siteType;
|
||||||
private String masterPassword;
|
String siteVariant;
|
||||||
@XmlElement
|
String siteContext;
|
||||||
private String keyID;
|
String result;
|
||||||
@XmlElement
|
|
||||||
private String siteName;
|
|
||||||
@XmlElement
|
|
||||||
private Integer siteCounter;
|
|
||||||
@XmlElement
|
|
||||||
private String siteType;
|
|
||||||
@XmlElement
|
|
||||||
private String siteVariant;
|
|
||||||
@XmlElement
|
|
||||||
private String siteContext;
|
|
||||||
@XmlElement
|
|
||||||
private String result;
|
|
||||||
|
|
||||||
private transient Case parentCase;
|
private transient Case parentCase;
|
||||||
|
|
||||||
@ -84,10 +69,10 @@ public class MPTests {
|
|||||||
parentCase.initializeParentHierarchy( tests );
|
parentCase.initializeParentHierarchy( tests );
|
||||||
}
|
}
|
||||||
|
|
||||||
algorithm = ifNotNullElse( algorithm, new NNSupplier<String>() {
|
algorithm = ifNotNullElse( algorithm, new NNSupplier<Integer>() {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public String get() {
|
public Integer get() {
|
||||||
return checkNotNull( parentCase.algorithm );
|
return checkNotNull( parentCase.algorithm );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
@ -119,10 +104,10 @@ public class MPTests {
|
|||||||
return checkNotNull( parentCase.siteName );
|
return checkNotNull( parentCase.siteName );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
|
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<UnsignedInteger>() {
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@Override
|
@Override
|
||||||
public Integer get() {
|
public UnsignedInteger get() {
|
||||||
return checkNotNull( parentCase.siteCounter );
|
return checkNotNull( parentCase.siteCounter );
|
||||||
}
|
}
|
||||||
} );
|
} );
|
||||||
@ -168,7 +153,7 @@ public class MPTests {
|
|||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
public MasterKey.Version getAlgorithm() {
|
public MasterKey.Version getAlgorithm() {
|
||||||
return MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( algorithm ) );
|
return MasterKey.Version.fromInt( checkNotNull( algorithm ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
@ -191,8 +176,8 @@ public class MPTests {
|
|||||||
return checkNotNull( siteName );
|
return checkNotNull( siteName );
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSiteCounter() {
|
public UnsignedInteger getSiteCounter() {
|
||||||
return ifNotNullElse( siteCounter, 1 );
|
return ifNotNullElse( siteCounter, UnsignedInteger.valueOf( 1 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nonnull
|
@Nonnull
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<tests>
|
<tests>
|
||||||
<!-- Default values for all parameters. -->
|
<!-- Default values for all parameters. -->
|
||||||
<case id="default">
|
<case id="default">
|
||||||
<algorithm><!-- current --></algorithm>
|
<algorithm>-1</algorithm>
|
||||||
<fullName>Robert Lee Mitchell</fullName>
|
<fullName>Robert Lee Mitchell</fullName>
|
||||||
<masterPassword>banana colored duckling</masterPassword>
|
<masterPassword>banana colored duckling</masterPassword>
|
||||||
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
||||||
|
Loading…
Reference in New Issue
Block a user