2
0

Java relocation.

This commit is contained in:
Maarten Billemont
2018-06-05 20:19:10 -04:00
parent aef8422102
commit 190a241a25
57 changed files with 5 additions and 5 deletions

View File

@@ -0,0 +1,19 @@
plugins {
id 'java'
}
description = 'Master Password Test Suite'
dependencies {
implementation group: 'com.lyndir.lhunath.opal', name: 'opal-system', version: '1.7-p1'
compile project( ':masterpassword-algorithm' )
compile project( ':masterpassword-model' )
testCompile group: 'org.testng', name: 'testng', version: '6.8.5'
testCompile group: 'ch.qos.logback', name: 'logback-classic', version: '1.1.2'
}
//tasks.withType(Test) {
// systemProperty "java.library.path", "/Users/lhunath/Documents/workspace/lyndir/MasterPassword/core/java/algorithm/build/libs/mpw/shared"
//}
test.useTestNG()

View File

@@ -0,0 +1,204 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
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 java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.concurrent.Callable;
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
*/
@SuppressWarnings({ "HardCodedStringLiteral", "ProhibitedExceptionDeclared" })
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 final MPTests tests;
private Listener listener;
public MPTestSuite()
throws UnavailableException {
this( DEFAULT_RESOURCE_NAME );
}
public MPTestSuite(final String resourceName)
throws UnavailableException {
try {
tests = new MPTests();
tests.cases = Lists.newLinkedList();
SAXParser parser = SAXParserFactory.newInstance().newSAXParser();
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources( "." );
parser.parse( Thread.currentThread().getContextClassLoader().getResourceAsStream( resourceName ), new DefaultHandler2() {
private final Deque<String> currentTags = Lists.newLinkedList();
private final 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 = Preconditions.checkNotNull( currentTexts.pop() ).toString();
if ("case".equals( qName ))
tests.cases.add( currentCase );
if ("algorithm".equals( qName ))
currentCase.algorithm = ConversionUtils.toInteger( text ).orElse( null );
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 ("resultType".equals( qName ))
currentCase.resultType = text;
if ("keyPurpose".equals( qName ))
currentCase.keyPurpose = text;
if ("keyContext".equals( qName ))
currentCase.keyContext = 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 );
Preconditions.checkNotNull( currentTexts.peek() ).append( ch, start, length );
}
} );
}
catch (final IllegalArgumentException | ParserConfigurationException | SAXException | IOException e) {
throw new UnavailableException( e );
}
for (final MPTests.Case testCase : tests.getCases())
testCase.initializeParentHierarchy( tests );
}
public void setListener(final Listener listener) {
this.listener = listener;
}
public MPTests getTests() {
return tests;
}
public boolean forEach(final String testName, final TestCase testFunction)
throws Exception {
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;
progress( Logger.Target.INFO, c, cases.size(), //
"[%s] on %s...", testName, testCase.getIdentifier() );
if (!testFunction.run( testCase )) {
progress( Logger.Target.ERROR, cases.size(), cases.size(), //
"[%s] on %s: FAILED!", testName, testCase.getIdentifier() );
return false;
}
progress( Logger.Target.INFO, c + 1, cases.size(), //
"[%s] on %s: passed!", testName, testCase.getIdentifier() );
}
return true;
}
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", testCase -> {
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), testCase.getMasterPassword().toCharArray() );
String sitePassword = masterKey.siteResult( testCase.getSiteName(), testCase.getAlgorithm(), testCase.getSiteCounter(),
testCase.getKeyPurpose(), testCase.getKeyContext(),
testCase.getResultType(), null );
return testCase.getResult().equals( sitePassword );
} );
}
public static class UnavailableException extends Exception {
private static final long serialVersionUID = 1L;
public UnavailableException(final Throwable cause) {
super( cause );
}
}
@FunctionalInterface
public interface Listener {
void progress(int current, int max, String messageFormat, Object... args);
}
@FunctionalInterface
public interface TestCase {
boolean run(MPTests.Case testCase)
throws Exception;
}
}

View File

@@ -0,0 +1,246 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static com.google.common.base.Preconditions.*;
import static com.lyndir.lhunath.opal.system.util.ObjectUtils.*;
import static com.lyndir.lhunath.opal.system.util.StringUtils.*;
import com.google.common.primitives.UnsignedInteger;
import com.lyndir.lhunath.opal.system.logging.Logger;
import com.lyndir.lhunath.opal.system.util.NNSupplier;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.bind.annotation.XmlTransient;
/**
* @author lhunath, 14-12-05
*/
public class MPTests {
private static final String ID_DEFAULT = "default";
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPTests.class );
private final Set<String> filters = new HashSet<>();
List<Case> cases;
@Nonnull
public List<Case> getCases() {
if (filters.isEmpty())
return checkNotNull( cases );
return checkNotNull( cases ).stream().filter( testCase -> {
for (final String filter : filters)
if (testCase.getIdentifier().startsWith( filter ))
return true;
return false;
} ).collect( Collectors.toList() );
}
public Case getCase(final String identifier) {
for (final Case testCase : cases)
if (identifier.equals( testCase.getIdentifier() ))
return testCase;
throw new IllegalArgumentException( strf( "No case for identifier: %s", identifier ) );
}
public Case getDefaultCase() {
try {
return getCase( ID_DEFAULT );
}
catch (final IllegalArgumentException e) {
throw new IllegalStateException( strf( "Missing default case in test suite. Add a case with id: %s", ID_DEFAULT ), e );
}
}
public boolean addFilters(final String... filters) {
return this.filters.addAll( Arrays.asList( filters ) );
}
public static class Case {
String identifier;
String parent;
@Nullable
Integer algorithm;
String fullName;
String masterPassword;
String keyID;
String siteName;
@Nullable
UnsignedInteger siteCounter;
String resultType;
String keyPurpose;
String keyContext;
String result;
@XmlTransient
private Case parentCase;
public void initializeParentHierarchy(final MPTests tests) {
if (parent != null) {
parentCase = tests.getCase( parent );
parentCase.initializeParentHierarchy( tests );
}
algorithm = ifNotNullElse( algorithm, new NNSupplier<Integer>() {
@Nonnull
@Override
public Integer get() {
return checkNotNull( parentCase.algorithm );
}
} );
fullName = ifNotNullElse( fullName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.fullName );
}
} );
masterPassword = ifNotNullElse( masterPassword, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.masterPassword );
}
} );
keyID = ifNotNullElse( keyID, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.keyID );
}
} );
siteName = ifNotNullElse( siteName, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.siteName );
}
} );
siteCounter = ifNotNullElse( siteCounter, new NNSupplier<UnsignedInteger>() {
@Nonnull
@Override
public UnsignedInteger get() {
return checkNotNull( parentCase.siteCounter );
}
} );
resultType = ifNotNullElse( resultType, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.resultType );
}
} );
keyPurpose = ifNotNullElse( keyPurpose, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return checkNotNull( parentCase.keyPurpose );
}
} );
keyContext = ifNotNullElse( keyContext, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return (parentCase == null)? "": checkNotNull( parentCase.keyContext );
}
} );
result = ifNotNullElse( result, new NNSupplier<String>() {
@Nonnull
@Override
public String get() {
return (parentCase == null)? "": checkNotNull( parentCase.result );
}
} );
}
@Nonnull
public String getIdentifier() {
return identifier;
}
@Nullable
public Case getParentCase() {
return parentCase;
}
@Nonnull
public MPAlgorithm getAlgorithm() {
return MPAlgorithm.Version.fromInt( checkNotNull( algorithm ) ).getAlgorithm();
}
@Nonnull
public String getFullName() {
return checkNotNull( fullName );
}
@Nonnull
public String getMasterPassword() {
return checkNotNull( masterPassword );
}
@Nonnull
public String getKeyID() {
return checkNotNull( keyID );
}
@Nonnull
public String getSiteName() {
return checkNotNull( siteName );
}
public UnsignedInteger getSiteCounter() {
return ifNotNullElse( siteCounter, UnsignedInteger.valueOf( 1 ) );
}
@Nonnull
public MPResultType getResultType() {
return MPResultType.forName( checkNotNull( resultType ) );
}
@Nonnull
public MPKeyPurpose getKeyPurpose() {
return MPKeyPurpose.forName( checkNotNull( keyPurpose ) );
}
@Nonnull
public String getKeyContext() {
return checkNotNull( keyContext );
}
@Nonnull
public String getResult() {
return checkNotNull( result );
}
@Override
public String toString() {
return identifier;
}
}
}

View File

@@ -0,0 +1 @@
../../../../../c/cli/mpw_tests.xml

View File

@@ -0,0 +1,67 @@
{
"export": {
"format": 1,
"redacted": true,
"date": "2018-05-10T03:41:18Z",
"_ext_mpw": {
"save": "me"
},
"_ext_other": {
"save": "me"
}
},
"user": {
"avatar": 3,
"full_name": "Robert Lee Mitchell",
"last_used": "2018-05-10T03:41:18Z",
"key_id": "98EEF4D1DF46D849574A82A03C3177056B15DFFCA29BB3899DE4628453675302",
"algorithm": 3,
"default_type": 17,
"_ext_mpw": {
"save": "me"
},
"_ext_other": {
"save": "me"
}
},
"sites": {
"masterpasswordapp.com": {
"type": 17,
"counter": 1,
"algorithm": 3,
"login_type": 30,
"uses": 2,
"last_used": "2018-05-10T03:41:18Z",
"questions": {
"": {
"type": 31
},
"mother": {
"type": 31
}
},
"_ext_mpw": {
"url": "https://masterpasswordapp.com",
"save": "me"
},
"_ext_other": {
"save": "me"
}
},
"personal.site": {
"type": 1056,
"counter": 1,
"algorithm": 3,
"password": "ZTgr4cY6L28wG7DsO+iz\/hrTQxM3UHz0x8ZU99LjgxjHG+bLIJygkbg\/7HdjEIFH6A3z+Dt2H1gpt9yPyQGZcewTiPXJX0pNpVsIKAAdzVNcUfYoqkWjoFRoZD7sM\/ctxWDH4JUuJ+rjoBkWtRLK9kYBvu7UD1QdlEZI\/wPKv1A=",
"login_type": 30,
"uses": 1,
"last_used": "2018-05-10T03:48:35Z"
}
},
"_ext_mpw": {
"save": "me"
},
"_ext_other": {
"save": "me"
}
}

View File

@@ -0,0 +1,139 @@
//==============================================================================
// This file is part of Master Password.
// Copyright (c) 2011-2017, Maarten Billemont.
//
// Master Password is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Master Password is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You can find a copy of the GNU General Public License in the
// LICENSE file. Alternatively, see <http://www.gnu.org/licenses/>.
//==============================================================================
package com.lyndir.masterpassword;
import static org.testng.Assert.*;
import com.lyndir.lhunath.opal.system.CodeUtils;
import com.lyndir.lhunath.opal.system.logging.Logger;
import java.util.Random;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class MPMasterKeyTest {
@SuppressWarnings("UnusedDeclaration")
private static final Logger logger = Logger.get( MPMasterKeyTest.class );
private MPTestSuite testSuite;
@BeforeMethod
public void setUp()
throws Exception {
testSuite = new MPTestSuite();
//testSuite.getTests().addFilters( "v3_type_maximum" );
}
@Test
public void testMasterKey()
throws Exception {
testSuite.forEach( "testMasterKey", testCase -> {
char[] masterPassword = testCase.getMasterPassword().toCharArray();
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
// Test key
assertEquals(
CodeUtils.encodeHex( masterKey.getKeyID( testCase.getAlgorithm() ) ),
testCase.getKeyID(),
"[testMasterKey] keyID mismatch for test case: " + testCase );
// Test invalidation
masterKey.invalidate();
try {
masterKey.getKeyID( testCase.getAlgorithm() );
fail( "[testMasterKey] invalidate ineffective for test case: " + testCase );
}
catch (final MPKeyUnavailableException ignored) {
}
assertNotEquals(
masterPassword,
testCase.getMasterPassword().toCharArray(),
"[testMasterKey] masterPassword not wiped for test case: " + testCase );
return true;
} );
}
@Test
public void testSiteResult()
throws Exception {
testSuite.forEach( "testSiteResult", testCase -> {
char[] masterPassword = testCase.getMasterPassword().toCharArray();
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
// Test site result
assertEquals(
masterKey.siteResult( testCase.getSiteName(), testCase.getAlgorithm(), testCase.getSiteCounter(),
testCase.getKeyPurpose(),
testCase.getKeyContext(), testCase.getResultType(),
null ),
testCase.getResult(),
"[testSiteResult] result mismatch for test case: " + testCase );
return true;
} );
}
@Test
public void testSiteState()
throws Exception {
MPTests.Case testCase = testSuite.getTests().getDefaultCase();
char[] masterPassword = testCase.getMasterPassword().toCharArray();
MPMasterKey masterKey = new MPMasterKey( testCase.getFullName(), masterPassword );
String password = randomString( 8 );
MPResultType resultType = MPResultType.StoredPersonal;
for (final MPAlgorithm.Version version : MPAlgorithm.Version.values()) {
MPAlgorithm algorithm = version.getAlgorithm();
// Test site state
String state = masterKey.siteState( testCase.getSiteName(), algorithm, testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), resultType, password );
String result = masterKey.siteResult( testCase.getSiteName(), algorithm, testCase.getSiteCounter(), testCase.getKeyPurpose(),
testCase.getKeyContext(), resultType, state );
assertEquals(
result,
password,
"[testSiteState] state mismatch for test case: " + testCase );
}
}
private static String randomString(int length) {
Random random = new Random();
StringBuilder builder = new StringBuilder();
while (length > 0) {
int codePoint = random.nextInt( Character.MAX_CODE_POINT - Character.MIN_CODE_POINT ) + Character.MIN_CODE_POINT;
if (!Character.isDefined( codePoint ) || (Character.getType( codePoint ) == Character.PRIVATE_USE) || Character.isSurrogate(
(char) codePoint ))
continue;
builder.appendCodePoint( codePoint );
length--;
}
return builder.toString();
}
}

View File

@@ -0,0 +1,15 @@
<configuration scan="false">
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%-8relative %22c{0} [%-5level] %msg%n</pattern>
</encoder>
</appender>
<logger name="com.lyndir" level="${mp.log.level:-TRACE}" />
<root level="INFO">
<appender-ref ref="stdout" />
</root>
</configuration>