add tests for MessageCryptoHelper
This commit is contained in:
parent
8b1cfb0730
commit
4d580d539d
6 changed files with 290 additions and 15 deletions
|
@ -57,7 +57,7 @@ public abstract class Message implements Part, Body {
|
|||
final int MULTIPLIER = 31;
|
||||
|
||||
int result = 1;
|
||||
result = MULTIPLIER * result + mFolder.getName().hashCode();
|
||||
result = MULTIPLIER * result + (mFolder != null ? mFolder.getName().hashCode() : 0);
|
||||
result = MULTIPLIER * result + mUid.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import android.os.Parcelable;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import com.fsck.k9.ui.crypto.OpenPgpApiFactory;
|
||||
import timber.log.Timber;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
|
@ -268,7 +270,7 @@ public class MessageLoaderHelper {
|
|||
messageCryptoHelper = retainCryptoHelperFragment.getData();
|
||||
}
|
||||
if (messageCryptoHelper == null || messageCryptoHelper.isConfiguredForOutdatedCryptoProvider()) {
|
||||
messageCryptoHelper = new MessageCryptoHelper(context);
|
||||
messageCryptoHelper = new MessageCryptoHelper(context, new OpenPgpApiFactory());
|
||||
retainCryptoHelperFragment.setData(messageCryptoHelper);
|
||||
}
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(
|
||||
|
|
|
@ -200,11 +200,9 @@ public class MessageDecryptVerifier {
|
|||
if (body instanceof Multipart) {
|
||||
Multipart multi = (Multipart) body;
|
||||
BodyPart signatureBody = multi.getBodyPart(1);
|
||||
if (isSameMimeType(signatureBody.getMimeType(), APPLICATION_PGP_SIGNATURE)) {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
signatureBody.getBody().writeTo(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
signatureBody.getBody().writeTo(bos);
|
||||
return bos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ import android.content.Intent;
|
|||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.WorkerThread;
|
||||
import timber.log.Timber;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.crypto.MessageDecryptVerifier;
|
||||
|
@ -24,6 +23,7 @@ import com.fsck.k9.mail.Address;
|
|||
import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.BodyPart;
|
||||
import com.fsck.k9.mail.Flag;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Multipart;
|
||||
import com.fsck.k9.mail.Part;
|
||||
|
@ -34,7 +34,6 @@ import com.fsck.k9.mail.internet.SizeAware;
|
|||
import com.fsck.k9.mail.internet.TextBody;
|
||||
import com.fsck.k9.mailstore.CryptoResultAnnotation;
|
||||
import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError;
|
||||
import com.fsck.k9.mailstore.LocalMessage;
|
||||
import com.fsck.k9.mailstore.MessageHelper;
|
||||
import com.fsck.k9.mailstore.MimePartStreamParser;
|
||||
import com.fsck.k9.mailstore.util.FileFactory;
|
||||
|
@ -52,6 +51,7 @@ import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
|
|||
import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||
import org.openintents.openpgp.util.OpenPgpServiceConnection.OnBound;
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class MessageCryptoHelper {
|
||||
|
@ -68,7 +68,7 @@ public class MessageCryptoHelper {
|
|||
@Nullable
|
||||
private MessageCryptoCallback callback;
|
||||
|
||||
private LocalMessage currentMessage;
|
||||
private Message currentMessage;
|
||||
private OpenPgpDecryptionResult cachedDecryptionResult;
|
||||
private MessageCryptoAnnotations queuedResult;
|
||||
private PendingIntent queuedPendingIntent;
|
||||
|
@ -84,15 +84,17 @@ public class MessageCryptoHelper {
|
|||
|
||||
private OpenPgpApi openPgpApi;
|
||||
private OpenPgpServiceConnection openPgpServiceConnection;
|
||||
private OpenPgpApiFactory openPgpApiFactory;
|
||||
|
||||
|
||||
public MessageCryptoHelper(Context context) {
|
||||
public MessageCryptoHelper(Context context, OpenPgpApiFactory openPgpApiFactory) {
|
||||
this.context = context.getApplicationContext();
|
||||
|
||||
if (!K9.isOpenPgpProviderConfigured()) {
|
||||
throw new IllegalStateException("MessageCryptoHelper must only be called with a openpgp provider!");
|
||||
}
|
||||
|
||||
this.openPgpApiFactory = openPgpApiFactory;
|
||||
openPgpProviderPackage = K9.getOpenPgpProvider();
|
||||
}
|
||||
|
||||
|
@ -100,7 +102,7 @@ public class MessageCryptoHelper {
|
|||
return !openPgpProviderPackage.equals(K9.getOpenPgpProvider());
|
||||
}
|
||||
|
||||
public void asyncStartOrResumeProcessingMessage(LocalMessage message, MessageCryptoCallback callback,
|
||||
public void asyncStartOrResumeProcessingMessage(Message message, MessageCryptoCallback callback,
|
||||
OpenPgpDecryptionResult cachedDecryptionResult) {
|
||||
if (this.currentMessage != null) {
|
||||
reattachCallback(message, callback);
|
||||
|
@ -215,9 +217,10 @@ public class MessageCryptoHelper {
|
|||
private void connectToCryptoProviderService() {
|
||||
openPgpServiceConnection = new OpenPgpServiceConnection(context, openPgpProviderPackage,
|
||||
new OnBound() {
|
||||
|
||||
@Override
|
||||
public void onBound(IOpenPgpService2 service) {
|
||||
openPgpApi = new OpenPgpApi(context, service);
|
||||
openPgpApi = openPgpApiFactory.createOpenPgpApi(context, service);
|
||||
|
||||
decryptOrVerifyNextPart();
|
||||
}
|
||||
|
@ -621,7 +624,7 @@ public class MessageCryptoHelper {
|
|||
}
|
||||
}
|
||||
|
||||
private void reattachCallback(LocalMessage message, MessageCryptoCallback callback) {
|
||||
private void reattachCallback(Message message, MessageCryptoCallback callback) {
|
||||
if (!message.equals(currentMessage)) {
|
||||
throw new AssertionError("Callback may only be reattached for the same message!");
|
||||
}
|
||||
|
@ -726,5 +729,4 @@ public class MessageCryptoHelper {
|
|||
return NO_REPLACEMENT_PART;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package com.fsck.k9.ui.crypto;
|
||||
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.IOpenPgpService2;
|
||||
|
||||
|
||||
public class OpenPgpApiFactory {
|
||||
OpenPgpApi createOpenPgpApi(Context context, IOpenPgpService2 service) {
|
||||
return new OpenPgpApi(context, service);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
package com.fsck.k9.ui.crypto;
|
||||
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.mail.Address;
|
||||
import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.BodyPart;
|
||||
import com.fsck.k9.mail.Message;
|
||||
import com.fsck.k9.mail.Multipart;
|
||||
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||
import com.fsck.k9.mail.internet.MimeMessage;
|
||||
import com.fsck.k9.mail.internet.MimeMultipart;
|
||||
import com.fsck.k9.mail.internet.TextBody;
|
||||
import com.fsck.k9.mailstore.CryptoResultAnnotation;
|
||||
import com.fsck.k9.mailstore.CryptoResultAnnotation.CryptoError;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.openintents.openpgp.IOpenPgpService2;
|
||||
import org.openintents.openpgp.OpenPgpDecryptionResult;
|
||||
import org.openintents.openpgp.OpenPgpSignatureResult;
|
||||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpSinkResultCallback;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSink;
|
||||
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
import static junit.framework.Assert.assertEquals;
|
||||
import static junit.framework.Assert.assertSame;
|
||||
import static junit.framework.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, sdk = 21)
|
||||
public class MessageCryptoHelperTest {
|
||||
private MessageCryptoHelper messageCryptoHelper;
|
||||
private OpenPgpApi openPgpApi;
|
||||
private Intent capturedApiIntent;
|
||||
private IOpenPgpSinkResultCallback capturedCallback;
|
||||
private MessageCryptoCallback messageCryptoCallback;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
openPgpApi = mock(OpenPgpApi.class);
|
||||
|
||||
K9.setOpenPgpProvider("org.example.dummy");
|
||||
|
||||
OpenPgpApiFactory openPgpApiFactory = mock(OpenPgpApiFactory.class);
|
||||
when(openPgpApiFactory.createOpenPgpApi(any(Context.class), any(IOpenPgpService2.class))).thenReturn(openPgpApi);
|
||||
messageCryptoHelper = new MessageCryptoHelper(RuntimeEnvironment.application, openPgpApiFactory);
|
||||
messageCryptoCallback = mock(MessageCryptoCallback.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void textPlain() throws Exception {
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "text/plain");
|
||||
|
||||
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
|
||||
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
|
||||
MessageCryptoAnnotations annotations = captor.getValue();
|
||||
assertTrue(annotations.isEmpty());
|
||||
verifyNoMoreInteractions(messageCryptoCallback);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartSigned__withNullBody__shouldReturnSignedIncomplete() throws Exception {
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "multipart/signed");
|
||||
|
||||
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_SIGNED_BUT_INCOMPLETE, null,
|
||||
null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartEncrypted__withNullBody__shouldReturnEncryptedIncomplete() throws Exception {
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "multipart/encrypted");
|
||||
|
||||
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
assertPartAnnotationHasState(
|
||||
message, messageCryptoCallback, CryptoError.OPENPGP_ENCRYPTED_BUT_INCOMPLETE, null, null, null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartEncrypted__withUnknownProtocol__shouldReturnEncryptedUnsupported() throws Exception {
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "multipart/encrypted; protocol=\"unknown protocol\"");
|
||||
message.setBody(new MimeMultipart("multipart/encrypted", "--------"));
|
||||
|
||||
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.ENCRYPTED_BUT_UNSUPPORTED, null, null,
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartSigned__withUnknownProtocol__shouldReturnSignedUnsupported() throws Exception {
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "multipart/signed; protocol=\"unknown protocol\"");
|
||||
message.setBody(new MimeMultipart("multipart/encrypted", "--------"));
|
||||
|
||||
MessageCryptoCallback messageCryptoCallback = mock(MessageCryptoCallback.class);
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.SIGNED_BUT_UNSUPPORTED, null, null,
|
||||
null, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartSigned__shouldCallOpenPgpApiAsync() throws Exception {
|
||||
BodyPart signedBodyPart = spy(new MimeBodyPart(new TextBody("text")));
|
||||
BodyPart signatureBodyPart = new MimeBodyPart(new TextBody("text"));
|
||||
|
||||
Multipart messageBody = new MimeMultipart("boundary1");
|
||||
messageBody.addBodyPart(signedBodyPart);
|
||||
messageBody.addBodyPart(signatureBodyPart);
|
||||
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "multipart/signed; protocol=\"application/pgp-signature\"");
|
||||
message.setFrom(Address.parse("Test <test@example.org>")[0]);
|
||||
message.setBody(messageBody);
|
||||
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
|
||||
|
||||
processSignedMessageAndCaptureMocks(message, signedBodyPart, outputStream);
|
||||
|
||||
|
||||
assertEquals(OpenPgpApi.ACTION_DECRYPT_VERIFY, capturedApiIntent.getAction());
|
||||
assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipartEncrypted__shouldCallOpenPgpApiAsync() throws Exception {
|
||||
BodyPart dummyBodyPart = new MimeBodyPart(new TextBody("text"));
|
||||
Body encryptedBody = spy(new TextBody("encrypted data"));
|
||||
BodyPart encryptedBodyPart = spy(new MimeBodyPart(encryptedBody));
|
||||
|
||||
Multipart messageBody = new MimeMultipart("boundary1");
|
||||
messageBody.addBodyPart(dummyBodyPart);
|
||||
messageBody.addBodyPart(encryptedBodyPart);
|
||||
|
||||
MimeMessage message = new MimeMessage();
|
||||
message.setUid("msguid");
|
||||
message.setHeader("Content-Type", "multipart/encrypted; protocol=\"application/pgp-encrypted\"");
|
||||
message.setFrom(Address.parse("Test <test@example.org>")[0]);
|
||||
message.setBody(messageBody);
|
||||
|
||||
OutputStream outputStream = mock(OutputStream.class);
|
||||
|
||||
Intent resultIntent = new Intent();
|
||||
resultIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
|
||||
OpenPgpDecryptionResult decryptionResult = mock(OpenPgpDecryptionResult.class);
|
||||
resultIntent.putExtra(OpenPgpApi.RESULT_DECRYPTION, decryptionResult);
|
||||
OpenPgpSignatureResult signatureResult = mock(OpenPgpSignatureResult.class);
|
||||
resultIntent.putExtra(OpenPgpApi.RESULT_SIGNATURE, signatureResult);
|
||||
PendingIntent pendingIntent = mock(PendingIntent.class);
|
||||
resultIntent.putExtra(OpenPgpApi.RESULT_INTENT, pendingIntent);
|
||||
|
||||
|
||||
processEncryptedMessageAndCaptureMocks(message, encryptedBody, outputStream);
|
||||
|
||||
MimeBodyPart decryptedPart = new MimeBodyPart(new TextBody("text"));
|
||||
capturedCallback.onReturn(resultIntent, decryptedPart);
|
||||
|
||||
|
||||
assertEquals(OpenPgpApi.ACTION_DECRYPT_VERIFY, capturedApiIntent.getAction());
|
||||
assertEquals("test@example.org", capturedApiIntent.getStringExtra(OpenPgpApi.EXTRA_SENDER_ADDRESS));
|
||||
assertPartAnnotationHasState(message, messageCryptoCallback, CryptoError.OPENPGP_OK, decryptedPart,
|
||||
decryptionResult, signatureResult, pendingIntent);
|
||||
}
|
||||
|
||||
private void processEncryptedMessageAndCaptureMocks(Message message, Body encryptedBody, OutputStream outputStream)
|
||||
throws Exception {
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
|
||||
ArgumentCaptor<IOpenPgpSinkResultCallback> callbackCaptor = ArgumentCaptor.forClass(IOpenPgpSinkResultCallback.class);
|
||||
verify(openPgpApi).executeApiAsync(intentCaptor.capture(), dataSourceCaptor.capture(),
|
||||
any(OpenPgpDataSink.class), callbackCaptor.capture());
|
||||
|
||||
capturedApiIntent = intentCaptor.getValue();
|
||||
capturedCallback = callbackCaptor.getValue();
|
||||
|
||||
OpenPgpDataSource dataSource = dataSourceCaptor.getValue();
|
||||
dataSource.writeTo(outputStream);
|
||||
verify(encryptedBody).writeTo(outputStream);
|
||||
}
|
||||
|
||||
private void processSignedMessageAndCaptureMocks(Message message, BodyPart signedBodyPart,
|
||||
OutputStream outputStream) throws Exception {
|
||||
messageCryptoHelper.asyncStartOrResumeProcessingMessage(message, messageCryptoCallback, null);
|
||||
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
ArgumentCaptor<OpenPgpDataSource> dataSourceCaptor = ArgumentCaptor.forClass(OpenPgpDataSource.class);
|
||||
ArgumentCaptor<IOpenPgpSinkResultCallback> callbackCaptor = ArgumentCaptor.forClass(IOpenPgpSinkResultCallback.class);
|
||||
verify(openPgpApi).executeApiAsync(intentCaptor.capture(), dataSourceCaptor.capture(),
|
||||
callbackCaptor.capture());
|
||||
|
||||
capturedApiIntent = intentCaptor.getValue();
|
||||
capturedCallback = callbackCaptor.getValue();
|
||||
|
||||
OpenPgpDataSource dataSource = dataSourceCaptor.getValue();
|
||||
dataSource.writeTo(outputStream);
|
||||
verify(signedBodyPart).writeTo(outputStream);
|
||||
}
|
||||
|
||||
private void assertPartAnnotationHasState(Message message, MessageCryptoCallback messageCryptoCallback,
|
||||
CryptoError cryptoErrorState, MimeBodyPart replacementPart, OpenPgpDecryptionResult openPgpDecryptionResult,
|
||||
OpenPgpSignatureResult openPgpSignatureResult, PendingIntent openPgpPendingIntent) {
|
||||
ArgumentCaptor<MessageCryptoAnnotations> captor = ArgumentCaptor.forClass(MessageCryptoAnnotations.class);
|
||||
verify(messageCryptoCallback).onCryptoOperationsFinished(captor.capture());
|
||||
MessageCryptoAnnotations annotations = captor.getValue();
|
||||
CryptoResultAnnotation cryptoResultAnnotation = annotations.get(message);
|
||||
assertEquals(cryptoErrorState, cryptoResultAnnotation.getErrorType());
|
||||
if (replacementPart != null) {
|
||||
assertSame(replacementPart, cryptoResultAnnotation.getReplacementData());
|
||||
}
|
||||
assertSame(openPgpDecryptionResult, cryptoResultAnnotation.getOpenPgpDecryptionResult());
|
||||
assertSame(openPgpSignatureResult, cryptoResultAnnotation.getOpenPgpSignatureResult());
|
||||
assertSame(openPgpPendingIntent, cryptoResultAnnotation.getOpenPgpPendingIntent());
|
||||
verifyNoMoreInteractions(messageCryptoCallback);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue