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>
|
||||
<groupId>com.lyndir.lhunath.opal</groupId>
|
||||
<artifactId>opal-system</artifactId>
|
||||
<version>1.6-p8</version>
|
||||
<version>1.6-p9</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>joda-time</groupId>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import java.util.Arrays;
|
||||
@ -74,7 +75,7 @@ public abstract class MasterKey {
|
||||
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);
|
||||
|
||||
public boolean isValid() {
|
||||
@ -106,7 +107,9 @@ public abstract class MasterKey {
|
||||
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);
|
||||
|
||||
|
@ -3,6 +3,7 @@ package com.lyndir.masterpassword;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lambdaworks.crypto.SCrypt;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
@ -10,14 +11,15 @@ import java.nio.*;
|
||||
import java.nio.charset.Charset;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.util.Arrays;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - does math with chars whose signedness was platform-dependent.
|
||||
* - miscounted the byte-length fromInt multi-byte site names.
|
||||
* - miscounted the byte-length fromInt multi-byte full names.
|
||||
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
||||
* - V1: miscounted the byte-length fromInt multi-byte site names.
|
||||
* - V0: does math with chars whose signedness was platform-dependent.
|
||||
*
|
||||
* @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,
|
||||
@Nullable final String siteContext) {
|
||||
@Override
|
||||
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( !siteName.isEmpty() );
|
||||
|
||||
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( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||
|
||||
if (siteCounter == 0)
|
||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (300 * 1000)) * 300 );
|
||||
|
||||
String siteScope = siteVariant.getScope();
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
@ -108,7 +111,7 @@ public class MasterKeyV0 extends MasterKey {
|
||||
int[] sitePasswordSeed = new int[sitePasswordSeedBytes.length];
|
||||
for (int i = 0; i < sitePasswordSeedBytes.length; ++i) {
|
||||
ByteBuffer buf = ByteBuffer.allocate( Integer.SIZE / Byte.SIZE ).order( ByteOrder.BIG_ENDIAN );
|
||||
Arrays.fill( buf.array(), sitePasswordSeedBytes[i] > 0? (byte)0x00: (byte) 0xFF );
|
||||
Arrays.fill( buf.array(), sitePasswordSeedBytes[i] > 0? (byte) 0x00: (byte) 0xFF );
|
||||
buf.position( 2 );
|
||||
buf.put( sitePasswordSeedBytes[i] ).rewind();
|
||||
sitePasswordSeed[i] = buf.getInt() & 0xFFFF;
|
||||
@ -135,8 +138,13 @@ public class MasterKeyV0 extends MasterKey {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected byte[] bytesForInt(final int integer) {
|
||||
return ByteBuffer.allocate( MP_intLen / Byte.SIZE ).order( MP_byteOrder ).putInt( integer ).array();
|
||||
protected byte[] bytesForInt(final int number) {
|
||||
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
|
||||
|
@ -2,15 +2,17 @@ package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.primitives.Bytes;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.lhunath.opal.system.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* bugs:
|
||||
* - miscounted the byte-length fromInt multi-byte site names.
|
||||
* - miscounted the byte-length fromInt multi-byte full names.
|
||||
* - V2: miscounted the byte-length fromInt multi-byte full names.
|
||||
* - V1: miscounted the byte-length fromInt multi-byte site names.
|
||||
*
|
||||
* @author lhunath, 2014-08-30
|
||||
*/
|
||||
@ -29,18 +31,19 @@ public class MasterKeyV1 extends MasterKeyV0 {
|
||||
return Version.V1;
|
||||
}
|
||||
|
||||
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
|
||||
@Nullable final String siteContext) {
|
||||
@Override
|
||||
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( !siteName.isEmpty() );
|
||||
|
||||
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( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||
|
||||
if (siteCounter == 0)
|
||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (300 * 1000)) * 300 );
|
||||
|
||||
String siteScope = siteVariant.getScope();
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
|
@ -2,14 +2,16 @@ package com.lyndir.masterpassword;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
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.logging.Logger;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@ -28,18 +30,19 @@ public class MasterKeyV2 extends MasterKeyV1 {
|
||||
return Version.V2;
|
||||
}
|
||||
|
||||
public String encode(final String siteName, final MPSiteType siteType, int siteCounter, final MPSiteVariant siteVariant,
|
||||
@Nullable final String siteContext) {
|
||||
@Override
|
||||
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( !siteName.isEmpty() );
|
||||
|
||||
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( "siteType: %d (%s)", siteType.ordinal(), siteType );
|
||||
|
||||
if (siteCounter == 0)
|
||||
siteCounter = (int) (System.currentTimeMillis() / (300 * 1000)) * 300;
|
||||
if (siteCounter.longValue() == 0)
|
||||
siteCounter = UnsignedInteger.valueOf( (System.currentTimeMillis() / (300 * 1000)) * 300 );
|
||||
|
||||
String siteScope = siteVariant.getScope();
|
||||
byte[] siteNameBytes = siteName.getBytes( MP_charset );
|
||||
|
@ -12,13 +12,13 @@
|
||||
android:icon="@drawable/icon"
|
||||
android:label="@string/app_name"
|
||||
android:allowBackup="true">
|
||||
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme">
|
||||
<activity android:name=".TestActivity" android:theme="@style/MPTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name=".UsersActivity" />
|
||||
<activity android:name=".EmergencyActivity" android:theme="@style/MPTheme" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -107,6 +107,13 @@
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lyndir.masterpassword</groupId>
|
||||
<artifactId>masterpassword-tests</artifactId>
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>com.jakewharton</groupId>
|
||||
<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="siteVersion_hint">Algorithm</string>
|
||||
<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>
|
||||
|
@ -1,5 +1,6 @@
|
||||
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 android.app.*;
|
||||
@ -16,9 +17,9 @@ import android.widget.*;
|
||||
import butterknife.ButterKnife;
|
||||
import butterknife.InjectView;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import javax.annotation.Nullable;
|
||||
@ -87,6 +88,10 @@ public class EmergencyActivity extends Activity {
|
||||
private int hc_masterPassword;
|
||||
private String sitePassword;
|
||||
|
||||
public static void start(Context context) {
|
||||
context.startActivity( new Intent( context, EmergencyActivity.class ) );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate( savedInstanceState );
|
||||
@ -261,7 +266,7 @@ public class EmergencyActivity extends Activity {
|
||||
private void updateSitePassword() {
|
||||
final String siteName = siteNameField.getText().toString();
|
||||
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) {
|
||||
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.
|
||||
*/
|
||||
|
||||
|
||||
package com.lyndir.masterpassword;
|
||||
|
||||
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.collect.Maps;
|
||||
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.StringUtils;
|
||||
import java.io.*;
|
||||
@ -52,7 +52,7 @@ public class CLI {
|
||||
MPSiteType siteType = siteTypeName.isEmpty()? MPSiteType.GeneratedLong: MPSiteType.forOption( siteTypeName );
|
||||
MPSiteVariant variant = MPSiteVariant.Password;
|
||||
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.
|
||||
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 ))
|
||||
counterArg = true;
|
||||
else if (counterArg) {
|
||||
siteCounter = ConversionUtils.toIntegerNN( arg );
|
||||
siteCounter = UnsignedInteger.valueOf( arg );
|
||||
counterArg = false;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPSiteType;
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
|
||||
@ -11,10 +12,10 @@ public class IncognitoSite extends Site {
|
||||
|
||||
private String siteName;
|
||||
private MPSiteType siteType;
|
||||
private int siteCounter;
|
||||
private UnsignedInteger siteCounter;
|
||||
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) {
|
||||
this.siteName = siteName;
|
||||
this.siteType = siteType;
|
||||
@ -48,11 +49,11 @@ public class IncognitoSite extends Site {
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
}
|
||||
|
||||
public int getSiteCounter() {
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return siteCounter;
|
||||
}
|
||||
|
||||
public void setSiteCounter(final int siteCounter) {
|
||||
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||
this.siteCounter = siteCounter;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.lyndir.masterpassword.gui;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.MPSiteType;
|
||||
import com.lyndir.masterpassword.MasterKey;
|
||||
import com.lyndir.masterpassword.model.*;
|
||||
@ -55,13 +56,13 @@ public class ModelSite extends Site {
|
||||
}
|
||||
}
|
||||
|
||||
public int getSiteCounter() {
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return model.getSiteCounter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSiteCounter(final int siteCounter) {
|
||||
if (siteCounter != getSiteCounter()) {
|
||||
public void setSiteCounter(final UnsignedInteger siteCounter) {
|
||||
if (siteCounter.equals( getSiteCounter() )) {
|
||||
model.setSiteCounter( siteCounter );
|
||||
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.Iterables;
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.google.common.util.concurrent.*;
|
||||
import com.lyndir.lhunath.opal.system.util.PredicateNN;
|
||||
import com.lyndir.masterpassword.*;
|
||||
@ -62,51 +63,51 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
// Site Name
|
||||
sitePanel.add(Components.label("Site Name:"));
|
||||
sitePanel.add( Components.label( "Site Name:" ) );
|
||||
JComponent siteControls = Components.boxLayout( BoxLayout.LINE_AXIS, //
|
||||
siteNameField = Components.textField(), Components.stud(),
|
||||
siteActionButton = Components.button( "Add Site" ) );
|
||||
siteNameField.getDocument().addDocumentListener(this);
|
||||
siteNameField.addActionListener(new ActionListener() {
|
||||
siteNameField.getDocument().addDocumentListener( this );
|
||||
siteNameField.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
Futures.addCallback(updatePassword(true), new FutureCallback<String>() {
|
||||
Futures.addCallback( updatePassword( true ), new FutureCallback<String>() {
|
||||
@Override
|
||||
public void onSuccess(@Nullable final String sitePassword) {
|
||||
StringSelection clipboardContents = new StringSelection(sitePassword);
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(clipboardContents, null);
|
||||
StringSelection clipboardContents = new StringSelection( sitePassword );
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents( clipboardContents, null );
|
||||
|
||||
SwingUtilities.invokeLater(new Runnable() {
|
||||
SwingUtilities.invokeLater( new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
passwordField.setText(null);
|
||||
siteNameField.setText(null);
|
||||
passwordField.setText( null );
|
||||
siteNameField.setText( null );
|
||||
|
||||
dispatchEvent(new WindowEvent(PasswordFrame.this, WindowEvent.WINDOW_CLOSING));
|
||||
dispatchEvent( new WindowEvent( PasswordFrame.this, WindowEvent.WINDOW_CLOSING ) );
|
||||
}
|
||||
});
|
||||
} );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable t) {
|
||||
}
|
||||
});
|
||||
} );
|
||||
}
|
||||
});
|
||||
siteActionButton.addActionListener(new ActionListener() {
|
||||
} );
|
||||
siteActionButton.addActionListener( new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(final ActionEvent e) {
|
||||
if (currentSite == null)
|
||||
return;
|
||||
else if (currentSite instanceof ModelSite)
|
||||
PasswordFrame.this.user.deleteSite(currentSite);
|
||||
PasswordFrame.this.user.deleteSite( currentSite );
|
||||
else
|
||||
PasswordFrame.this.user.addSite(currentSite);
|
||||
PasswordFrame.this.user.addSite( currentSite );
|
||||
siteNameField.requestFocus();
|
||||
|
||||
updatePassword( true );
|
||||
}
|
||||
});
|
||||
} );
|
||||
sitePanel.add( siteControls );
|
||||
sitePanel.add( Components.stud() );
|
||||
|
||||
@ -118,14 +119,14 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
siteVersionField = Components.comboBox( MasterKey.Version.values() ), //
|
||||
Components.stud(), //
|
||||
siteCounterField = Components.spinner(
|
||||
new SpinnerNumberModel( 1, 1, Integer.MAX_VALUE, 1 ) ) );
|
||||
new SpinnerNumberModel( 1L, 1L, UnsignedInteger.MAX_VALUE, 1L ) ) );
|
||||
sitePanel.add( siteSettings );
|
||||
siteTypeField.setFont( Res.valueFont().deriveFont( 12f ) );
|
||||
siteTypeField.setSelectedItem( MPSiteType.GeneratedLong );
|
||||
siteTypeField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword(true);
|
||||
updatePassword( true );
|
||||
}
|
||||
} );
|
||||
|
||||
@ -135,7 +136,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
siteVersionField.addItemListener( new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(final ItemEvent e) {
|
||||
updatePassword(true);
|
||||
updatePassword( true );
|
||||
}
|
||||
} );
|
||||
|
||||
@ -144,7 +145,7 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
siteCounterField.addChangeListener( new ChangeListener() {
|
||||
@Override
|
||||
public void stateChanged(final ChangeEvent e) {
|
||||
updatePassword(true);
|
||||
updatePassword( true );
|
||||
}
|
||||
} );
|
||||
|
||||
@ -161,11 +162,11 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
|
||||
// Password
|
||||
passwordField = Components.passwordField();
|
||||
passwordField.setAlignmentX(Component.CENTER_ALIGNMENT);
|
||||
passwordField.setHorizontalAlignment(JTextField.CENTER);
|
||||
passwordField.putClientProperty("JPasswordField.cutCopyAllowed", true);
|
||||
passwordField.setEditable(false);
|
||||
passwordField.setBackground(null);
|
||||
passwordField.setAlignmentX( Component.CENTER_ALIGNMENT );
|
||||
passwordField.setHorizontalAlignment( JTextField.CENTER );
|
||||
passwordField.putClientProperty( "JPasswordField.cutCopyAllowed", true );
|
||||
passwordField.setEditable( false );
|
||||
passwordField.setBackground( null );
|
||||
passwordField.setBorder( null );
|
||||
passwordEchoChar = passwordField.getEchoChar();
|
||||
passwordEchoFont = passwordField.getFont().deriveFont( 40f );
|
||||
@ -174,7 +175,8 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
// Tip
|
||||
tipLabel = Components.label( " ", SwingConstants.CENTER );
|
||||
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.setBackground( Color.white );
|
||||
passwordContainer.setBorder( BorderFactory.createEmptyBorder( 8, 8, 8, 8 ) );
|
||||
@ -202,27 +204,27 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
if (updatingUI)
|
||||
return Futures.immediateCancelledFuture();
|
||||
if (siteNameQuery == null || siteNameQuery.isEmpty() || !user.isKeyAvailable()) {
|
||||
siteActionButton.setVisible(false);
|
||||
siteActionButton.setVisible( false );
|
||||
tipLabel.setText( null );
|
||||
passwordField.setText( null );
|
||||
return Futures.immediateCancelledFuture();
|
||||
}
|
||||
|
||||
final MPSiteType siteType = siteTypeField.getModel().getElementAt(siteTypeField.getSelectedIndex());
|
||||
final MasterKey.Version siteVersion = siteVersionField.getItemAt(siteVersionField.getSelectedIndex());
|
||||
final int siteCounter = (Integer) siteCounterField.getValue();
|
||||
final MPSiteType siteType = siteTypeField.getModel().getElementAt( siteTypeField.getSelectedIndex() );
|
||||
final MasterKey.Version siteVersion = siteVersionField.getItemAt( siteVersionField.getSelectedIndex() );
|
||||
final UnsignedInteger siteCounter = UnsignedInteger.valueOf( ((Number) siteCounterField.getValue()).longValue() );
|
||||
|
||||
Iterable<Site> siteResults = user.findSitesByName(siteNameQuery);
|
||||
Iterable<Site> siteResults = user.findSitesByName( siteNameQuery );
|
||||
if (!allowNameCompletion)
|
||||
siteResults = FluentIterable.from(siteResults).filter(new PredicateNN<Site>() {
|
||||
siteResults = FluentIterable.from( siteResults ).filter( new PredicateNN<Site>() {
|
||||
@Override
|
||||
public boolean apply(Site input) {
|
||||
return siteNameQuery.equals(input.getSiteName());
|
||||
return siteNameQuery.equals( input.getSiteName() );
|
||||
}
|
||||
});
|
||||
final Site site = Iterables.getFirst(siteResults,
|
||||
new IncognitoSite(siteNameQuery, siteType, siteCounter, siteVersion) );
|
||||
if (currentSite != null && site.getSiteName().equals(currentSite.getSiteName())) {
|
||||
} );
|
||||
final Site site = Iterables.getFirst( siteResults,
|
||||
new IncognitoSite( siteNameQuery, siteType, siteCounter, siteVersion ) );
|
||||
if (currentSite != null && currentSite.getSiteName().equals( site.getSiteName() )) {
|
||||
site.setSiteType( siteType );
|
||||
site.setAlgorithmVersion( siteVersion );
|
||||
site.setSiteCounter( siteCounter );
|
||||
@ -244,12 +246,12 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
public void run() {
|
||||
updatingUI = true;
|
||||
currentSite = site;
|
||||
siteActionButton.setVisible(user instanceof ModelUser);
|
||||
siteActionButton.setVisible( user instanceof ModelUser );
|
||||
if (currentSite instanceof ModelSite)
|
||||
siteActionButton.setText("Delete Site");
|
||||
siteActionButton.setText( "Delete Site" );
|
||||
else
|
||||
siteActionButton.setText("Add Site");
|
||||
siteTypeField.setSelectedItem(currentSite.getSiteType());
|
||||
siteActionButton.setText( "Add Site" );
|
||||
siteTypeField.setSelectedItem( currentSite.getSiteType() );
|
||||
siteVersionField.setSelectedItem( currentSite.getAlgorithmVersion() );
|
||||
siteCounterField.setValue( currentSite.getSiteCounter() );
|
||||
siteNameField.setText( currentSite.getSiteName() );
|
||||
@ -273,16 +275,16 @@ public class PasswordFrame extends JFrame implements DocumentListener {
|
||||
|
||||
@Override
|
||||
public void insertUpdate(final DocumentEvent e) {
|
||||
updatePassword(true);
|
||||
updatePassword( true );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeUpdate(final DocumentEvent e) {
|
||||
updatePassword(false);
|
||||
updatePassword( false );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changedUpdate(final DocumentEvent e) {
|
||||
updatePassword(true);
|
||||
updatePassword( true );
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package com.lyndir.masterpassword.gui;
|
||||
|
||||
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.MasterKey;
|
||||
|
||||
@ -23,9 +24,9 @@ public abstract class Site {
|
||||
|
||||
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
|
||||
public String toString() {
|
||||
|
@ -26,6 +26,11 @@
|
||||
<version>GIT-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<!-- EXTERNAL DEPENDENCIES -->
|
||||
<dependency>
|
||||
<groupId>joda-time</groupId>
|
||||
<artifactId>joda-time</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.auto.value</groupId>
|
||||
<artifactId>auto-value</artifactId>
|
||||
|
@ -2,6 +2,7 @@ package com.lyndir.masterpassword.model;
|
||||
|
||||
import static com.lyndir.lhunath.opal.system.util.StringUtils.strf;
|
||||
|
||||
import com.google.common.primitives.UnsignedInteger;
|
||||
import com.lyndir.masterpassword.*;
|
||||
import java.util.Objects;
|
||||
import javax.annotation.Nullable;
|
||||
@ -14,14 +15,14 @@ import org.joda.time.Instant;
|
||||
public class MPSite {
|
||||
|
||||
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 MasterKey.Version algorithmVersion;
|
||||
private Instant lastUsed;
|
||||
private String siteName;
|
||||
private MPSiteType siteType;
|
||||
private int siteCounter;
|
||||
private UnsignedInteger siteCounter;
|
||||
private int uses;
|
||||
private String loginName;
|
||||
|
||||
@ -29,7 +30,7 @@ public class MPSite {
|
||||
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.algorithmVersion = MasterKey.Version.CURRENT;
|
||||
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,
|
||||
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) {
|
||||
this.user = user;
|
||||
this.algorithmVersion = algorithmVersion;
|
||||
@ -101,11 +102,11 @@ public class MPSite {
|
||||
this.siteType = siteType;
|
||||
}
|
||||
|
||||
public int getSiteCounter() {
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return siteCounter;
|
||||
}
|
||||
|
||||
public void setSiteCounter(final int siteCounter) {
|
||||
public void setSiteCounter(final UnsignedInteger 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.collect.ImmutableList;
|
||||
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.logging.Logger;
|
||||
import com.lyndir.lhunath.opal.system.util.ConversionUtils;
|
||||
@ -139,7 +140,7 @@ public class MPSiteUnmarshaller {
|
||||
rfc3339.parseDateTime( siteMatcher.group( 1 ) ).toInstant(), //
|
||||
siteMatcher.group( 7 ), //
|
||||
MPSiteType.forType( ConversionUtils.toIntegerNN( siteMatcher.group( 3 ) ) ),
|
||||
ConversionUtils.toIntegerNN( siteMatcher.group( 5 ).replace( ":", "" ) ), //
|
||||
UnsignedInteger.valueOf( siteMatcher.group( 5 ).replace( ":", "" ) ), //
|
||||
ConversionUtils.toIntegerNN( siteMatcher.group( 2 ) ), //
|
||||
siteMatcher.group( 6 ), //
|
||||
siteMatcher.group( 8 ) );
|
||||
|
@ -1,24 +1,33 @@
|
||||
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.util.ConversionUtils;
|
||||
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.xml.bind.JAXBContext;
|
||||
import javax.xml.bind.JAXBException;
|
||||
import javax.xml.parsers.*;
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
||||
import org.xml.sax.ext.DefaultHandler2;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 2015-12-22
|
||||
*/
|
||||
public class MPTestSuite {
|
||||
public class MPTestSuite implements Callable<Boolean> {
|
||||
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTestSuite.class );
|
||||
private static final String DEFAULT_RESOURCE_NAME = "mpw_tests.xml";
|
||||
|
||||
private MPTests tests;
|
||||
private Listener listener;
|
||||
|
||||
public MPTestSuite()
|
||||
throws UnavailableException {
|
||||
@ -28,15 +37,78 @@ public class MPTestSuite {
|
||||
public MPTestSuite(String resourceName)
|
||||
throws UnavailableException {
|
||||
try {
|
||||
URL testCasesResource = Resources.getResource( resourceName );
|
||||
tests = (MPTests) JAXBContext.newInstance( MPTests.class ).createUnmarshaller().unmarshal( testCasesResource );
|
||||
tests = new MPTests();
|
||||
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())
|
||||
testCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
catch (IllegalArgumentException | JAXBException e) {
|
||||
throw new UnavailableException( e );
|
||||
}
|
||||
|
||||
public void setListener(final Listener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
public MPTests getTests() {
|
||||
@ -44,22 +116,39 @@ public class MPTestSuite {
|
||||
}
|
||||
|
||||
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())
|
||||
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 )) {
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
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>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
@ -79,4 +168,10 @@ public class MPTestSuite {
|
||||
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.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.util.*;
|
||||
import java.util.List;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.xml.bind.annotation.*;
|
||||
|
||||
|
||||
/**
|
||||
* @author lhunath, 14-12-05
|
||||
*/
|
||||
@XmlRootElement(name = "tests")
|
||||
public class MPTests {
|
||||
|
||||
private static final String ID_DEFAULT = "default";
|
||||
@ -22,8 +21,7 @@ public class MPTests {
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
private static final Logger logger = Logger.get( MPTests.class );
|
||||
|
||||
@XmlElement(name = "case")
|
||||
private List<Case> cases;
|
||||
List<Case> cases;
|
||||
|
||||
@Nonnull
|
||||
public List<Case> getCases() {
|
||||
@ -47,33 +45,20 @@ public class MPTests {
|
||||
}
|
||||
}
|
||||
|
||||
@XmlRootElement(name = "case")
|
||||
public static class Case {
|
||||
|
||||
@XmlAttribute(name = "id")
|
||||
private String identifier;
|
||||
@XmlAttribute
|
||||
private String parent;
|
||||
@XmlElement
|
||||
private String algorithm;
|
||||
@XmlElement
|
||||
private String fullName;
|
||||
@XmlElement
|
||||
private String masterPassword;
|
||||
@XmlElement
|
||||
private String keyID;
|
||||
@XmlElement
|
||||
private String siteName;
|
||||
@XmlElement
|
||||
private Integer siteCounter;
|
||||
@XmlElement
|
||||
private String siteType;
|
||||
@XmlElement
|
||||
private String siteVariant;
|
||||
@XmlElement
|
||||
private String siteContext;
|
||||
@XmlElement
|
||||
private String result;
|
||||
String identifier;
|
||||
String parent;
|
||||
Integer algorithm;
|
||||
String fullName;
|
||||
String masterPassword;
|
||||
String keyID;
|
||||
String siteName;
|
||||
UnsignedInteger siteCounter;
|
||||
String siteType;
|
||||
String siteVariant;
|
||||
String siteContext;
|
||||
String result;
|
||||
|
||||
private transient Case parentCase;
|
||||
|
||||
@ -84,10 +69,10 @@ public class MPTests {
|
||||
parentCase.initializeParentHierarchy( tests );
|
||||
}
|
||||
|
||||
algorithm = ifNotNullElse( algorithm, new NNSupplier<String>() {
|
||||
algorithm = ifNotNullElse( algorithm, new NNSupplier<Integer>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public String get() {
|
||||
public Integer get() {
|
||||
return checkNotNull( parentCase.algorithm );
|
||||
}
|
||||
} );
|
||||
@ -119,10 +104,10 @@ public class MPTests {
|
||||
return checkNotNull( parentCase.siteName );
|
||||
}
|
||||
} );
|
||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<Integer>() {
|
||||
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<UnsignedInteger>() {
|
||||
@Nonnull
|
||||
@Override
|
||||
public Integer get() {
|
||||
public UnsignedInteger get() {
|
||||
return checkNotNull( parentCase.siteCounter );
|
||||
}
|
||||
} );
|
||||
@ -168,7 +153,7 @@ public class MPTests {
|
||||
|
||||
@Nonnull
|
||||
public MasterKey.Version getAlgorithm() {
|
||||
return MasterKey.Version.fromInt( ConversionUtils.toIntegerNN( algorithm ) );
|
||||
return MasterKey.Version.fromInt( checkNotNull( algorithm ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
@ -191,8 +176,8 @@ public class MPTests {
|
||||
return checkNotNull( siteName );
|
||||
}
|
||||
|
||||
public int getSiteCounter() {
|
||||
return ifNotNullElse( siteCounter, 1 );
|
||||
public UnsignedInteger getSiteCounter() {
|
||||
return ifNotNullElse( siteCounter, UnsignedInteger.valueOf( 1 ) );
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
|
@ -1,7 +1,7 @@
|
||||
<tests>
|
||||
<!-- Default values for all parameters. -->
|
||||
<case id="default">
|
||||
<algorithm><!-- current --></algorithm>
|
||||
<algorithm>-1</algorithm>
|
||||
<fullName>Robert Lee Mitchell</fullName>
|
||||
<masterPassword>banana colored duckling</masterPassword>
|
||||
<keyID>98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302</keyID>
|
||||
|
Loading…
Reference in New Issue
Block a user