convert ComposeCryptoStatus to kotlin

This commit is contained in:
Vincent Breitmoser 2018-04-27 16:53:11 +02:00
parent e1015d325b
commit 6a19eb3d3a
10 changed files with 877 additions and 989 deletions

View file

@ -5,6 +5,7 @@ import java.io.InputStream;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
@ -51,7 +52,7 @@ public class AutocryptStatusInteractor {
}
}
@Nullable
@NonNull
private RecipientAutocryptStatusType getRecipientAutocryptStatusFromIntent(Intent result) {
boolean allKeysConfirmed = result.getBooleanExtra(OpenPgpApi.RESULT_KEYS_CONFIRMED, false);
int autocryptStatus =

View file

@ -1,16 +1,16 @@
package com.fsck.k9.message
interface CryptoStatus {
fun getOpenPgpKeyId(): Long?
val openPgpKeyId: Long?
fun isProviderStateOk(): Boolean
fun isSenderPreferEncryptMutual(): Boolean
val isSenderPreferEncryptMutual: Boolean
val isEncryptionEnabled: Boolean
val isPgpInlineModeEnabled: Boolean
val isSignOnly: Boolean
fun isSigningEnabled(): Boolean
fun isEncryptionEnabled(): Boolean
fun isPgpInlineModeEnabled(): Boolean
fun isSignOnly(): Boolean
fun isUserChoice(): Boolean
fun isReplyToEncrypted(): Boolean
val isReplyToEncrypted: Boolean
fun hasRecipients(): Boolean
fun isEncryptSubject(): Boolean
val isEncryptSubject: Boolean
fun getRecipientAddresses(): Array<String>
}

View file

@ -1,623 +0,0 @@
package com.fsck.k9.message;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import android.app.Activity;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import com.fsck.k9.Account.QuoteStyle;
import com.fsck.k9.Identity;
import com.fsck.k9.K9RobolectricTest;
import com.fsck.k9.TestCoreResourceProvider;
import com.fsck.k9.autocrypt.AutocryptOpenPgpApiInteractor;
import com.fsck.k9.autocrypt.AutocryptOperations;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.BodyPart;
import com.fsck.k9.mail.BoundaryGenerator;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.internet.MessageIdGenerator;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.message.MessageBuilder.Callback;
import com.fsck.k9.message.quote.InsertableHtmlContent;
import org.apache.commons.io.Charsets;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.util.MimeUtil;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.openintents.openpgp.util.OpenPgpApi;
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource;
import static com.fsck.k9.autocrypt.AutocryptOperationsHelper.assertMessageHasAutocryptHeader;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq;
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;
public class PgpMessageBuilderTest extends K9RobolectricTest {
private static final long TEST_KEY_ID = 123L;
private static final String TEST_MESSAGE_TEXT = "message text with a ☭ CCCP symbol";
private static final byte[] AUTOCRYPT_KEY_MATERIAL = { 1, 2, 3 };
private static final String SENDER_EMAIL = "test@example.org";
private CryptoStatus cryptoStatus = createCryptoStatus();
private OpenPgpApi openPgpApi = mock(OpenPgpApi.class);
private AutocryptOpenPgpApiInteractor autocryptOpenPgpApiInteractor = mock(AutocryptOpenPgpApiInteractor.class);
private PgpMessageBuilder pgpMessageBuilder = createDefaultPgpMessageBuilder(openPgpApi,
autocryptOpenPgpApiInteractor, cryptoStatus);
@Before
public void setUp() throws Exception {
when(autocryptOpenPgpApiInteractor.getKeyMaterialForKeyId(openPgpApi, TEST_KEY_ID, SENDER_EMAIL))
.thenReturn(AUTOCRYPT_KEY_MATERIAL);
}
@Test
public void build__withCryptoProviderNotOk__shouldThrow() {
configureEncryptAndSign();
when(cryptoStatus.isProviderStateOk()).thenReturn(false);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(mockCallback).onMessageBuildException(any(MessagingException.class));
verifyNoMoreInteractions(mockCallback);
}
@Test
public void buildCleartext__withNoSigningKey__shouldBuildTrivialMessage() {
configureCleartext();
when(cryptoStatus.getOpenPgpKeyId()).thenReturn(null);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage message = captor.getValue();
assertEquals("text/plain", message.getMimeType());
}
@Test
public void buildCleartext__shouldSucceed() {
configureCleartext();
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage message = captor.getValue();
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL);
}
@Test
public void buildSign__withNoDetachedSignatureInResult__shouldThrow() {
configureSignOnly();
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(mockCallback).onMessageBuildException(any(MessagingException.class));
verifyNoMoreInteractions(mockCallback);
}
@Test
public void buildSign__withDetachedSignatureInResult__shouldSucceed() {
configureSignOnly();
ArgumentCaptor<Intent> capturedApiIntent = ArgumentCaptor.forClass(Intent.class);
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
returnIntent.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, new byte[] { 1, 2, 3 });
when(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource.class),
nullable(OutputStream.class))).thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
Intent expectedIntent = new Intent(OpenPgpApi.ACTION_DETACHED_SIGN);
expectedIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID);
expectedIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
assertIntentEqualsActionAndExtras(expectedIntent, capturedApiIntent.getValue());
ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage message = captor.getValue();
Assert.assertEquals("message must be multipart/signed", "multipart/signed", message.getMimeType());
MimeMultipart multipart = (MimeMultipart) message.getBody();
Assert.assertEquals("multipart/signed must consist of two parts", 2, multipart.getCount());
BodyPart contentBodyPart = multipart.getBodyPart(0);
Assert.assertEquals("first part must have content type text/plain",
"text/plain", MimeUtility.getHeaderParameter(contentBodyPart.getContentType(), null));
assertTrue("signed message body must be TextBody", contentBodyPart.getBody() instanceof TextBody);
Assert.assertEquals(MimeUtil.ENC_QUOTED_PRINTABLE, ((TextBody) contentBodyPart.getBody()).getEncoding());
assertContentOfBodyPartEquals("content must match the message text", contentBodyPart, TEST_MESSAGE_TEXT);
BodyPart signatureBodyPart = multipart.getBodyPart(1);
String contentType = signatureBodyPart.getContentType();
Assert.assertEquals("second part must be pgp signature", "application/pgp-signature",
MimeUtility.getHeaderParameter(contentType, null));
Assert.assertEquals("second part must be called signature.asc", "signature.asc",
MimeUtility.getHeaderParameter(contentType, "name"));
assertContentOfBodyPartEquals("content must match the supplied detached signature",
signatureBodyPart, new byte[] { 1, 2, 3 });
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL);
}
@Test
public void buildSign__withUserInteractionResult__shouldReturnUserInteraction() {
configureSignOnly();
Intent returnIntent = mock(Intent.class);
when(returnIntent.getIntExtra(eq(OpenPgpApi.RESULT_CODE), anyInt()))
.thenReturn(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
final PendingIntent mockPendingIntent = mock(PendingIntent.class);
when(returnIntent.getParcelableExtra(eq(OpenPgpApi.RESULT_INTENT)))
.thenReturn(mockPendingIntent);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
ArgumentCaptor<PendingIntent> captor = ArgumentCaptor.forClass(PendingIntent.class);
verify(mockCallback).onMessageBuildReturnPendingIntent(captor.capture(), anyInt());
verifyNoMoreInteractions(mockCallback);
PendingIntent pendingIntent = captor.getValue();
Assert.assertSame(pendingIntent, mockPendingIntent);
}
@Test
public void buildSign__withReturnAfterUserInteraction__shouldSucceed() {
configureSignOnly();
int returnedRequestCode;
{
Intent returnIntent = spy(new Intent());
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED);
PendingIntent mockPendingIntent = mock(PendingIntent.class);
when(returnIntent.getParcelableExtra(eq(OpenPgpApi.RESULT_INTENT)))
.thenReturn(mockPendingIntent);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(returnIntent).getIntExtra(eq(OpenPgpApi.RESULT_CODE), anyInt());
ArgumentCaptor<PendingIntent> piCaptor = ArgumentCaptor.forClass(PendingIntent.class);
ArgumentCaptor<Integer> rcCaptor = ArgumentCaptor.forClass(Integer.class);
verify(mockCallback).onMessageBuildReturnPendingIntent(piCaptor.capture(), rcCaptor.capture());
verifyNoMoreInteractions(mockCallback);
returnedRequestCode = rcCaptor.getValue();
Assert.assertSame(mockPendingIntent, piCaptor.getValue());
}
{
Intent returnIntent = spy(new Intent());
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
Intent mockReturnIntent = mock(Intent.class);
when(openPgpApi.executeApi(same(mockReturnIntent), any(OpenPgpDataSource.class),
nullable(OutputStream.class))).thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.onActivityResult(returnedRequestCode, Activity.RESULT_OK, mockReturnIntent, mockCallback);
verify(openPgpApi).executeApi(same(mockReturnIntent), any(OpenPgpDataSource.class),
nullable(OutputStream.class));
verify(returnIntent).getIntExtra(eq(OpenPgpApi.RESULT_CODE), anyInt());
}
}
@Test
public void buildEncrypt__withoutRecipients__shouldThrow() {
configureEncryptAndSign();
Intent returnIntent = spy(new Intent());
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(mockCallback).onMessageBuildException(any(MessagingException.class));
verifyNoMoreInteractions(mockCallback);
}
@Test
public void buildEncrypt__checkGossip() {
configureEncryptAndSign();
configureRecipients("alice@example.org", "bob@example.org");
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
pgpMessageBuilder.buildAsync(mock(Callback.class));
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("alice@example.org"));
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("bob@example.org"));
}
@Test
public void buildEncrypt__checkGossip__filterBcc() {
configureEncryptAndSign();
configureRecipients("alice@example.org", "bob@example.org", "carol@example.org");
pgpMessageBuilder.setBcc(Collections.singletonList(new Address("carol@example.org")));
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
pgpMessageBuilder.buildAsync(mock(Callback.class));
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("alice@example.org"));
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("bob@example.org"));
}
@Test
public void buildEncrypt__checkGossip__filterBccSingleRecipient() {
configureEncryptAndSign();
configureRecipients("alice@example.org", "carol@example.org");
pgpMessageBuilder.setBcc(Collections.singletonList(new Address("carol@example.org")));
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntent);
pgpMessageBuilder.buildAsync(mock(Callback.class));
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForKeyId(any(OpenPgpApi.class), any(Long.class), any(String.class));
verifyNoMoreInteractions(autocryptOpenPgpApiInteractor);
}
@Test
public void buildEncrypt__shouldSucceed() {
configureEncryptAndSign();
configureRecipients("test@example.org");
ArgumentCaptor<Intent> capturedApiIntent = ArgumentCaptor.forClass(Intent.class);
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource.class),
nullable(OutputStream.class))).thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
Intent expectedApiIntent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[] { TEST_KEY_ID });
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, cryptoStatus.getRecipientAddresses());
assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.getValue());
ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage message = captor.getValue();
Assert.assertEquals("message must be multipart/encrypted", "multipart/encrypted", message.getMimeType());
MimeMultipart multipart = (MimeMultipart) message.getBody();
Assert.assertEquals("multipart/encrypted must consist of two parts", 2, multipart.getCount());
BodyPart dummyBodyPart = multipart.getBodyPart(0);
Assert.assertEquals("first part must be pgp encrypted dummy part",
"application/pgp-encrypted", dummyBodyPart.getContentType());
assertContentOfBodyPartEquals("content must match the supplied detached signature",
dummyBodyPart, "Version: 1");
BodyPart encryptedBodyPart = multipart.getBodyPart(1);
Assert.assertEquals("second part must be octet-stream of encrypted data",
"application/octet-stream; name=\"encrypted.asc\"", encryptedBodyPart.getContentType());
assertTrue("message body must be BinaryTempFileBody",
encryptedBodyPart.getBody() instanceof BinaryTempFileBody);
Assert.assertEquals(MimeUtil.ENC_7BIT, ((BinaryTempFileBody) encryptedBodyPart.getBody()).getEncoding());
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL);
}
@Test
public void buildEncrypt__withInlineEnabled__shouldSucceed() {
configureEncryptAndSign();
configureRecipients("test@example.org");
configurePgpInline();
ArgumentCaptor<Intent> capturedApiIntent = ArgumentCaptor.forClass(Intent.class);
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource.class),
nullable(OutputStream.class))).thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
Intent expectedApiIntent = new Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, new long[] { TEST_KEY_ID });
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, cryptoStatus.getRecipientAddresses());
assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.getValue());
ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage message = captor.getValue();
Assert.assertEquals("text/plain", message.getMimeType());
assertTrue("message body must be BinaryTempFileBody", message.getBody() instanceof BinaryTempFileBody);
Assert.assertEquals(MimeUtil.ENC_7BIT, ((BinaryTempFileBody) message.getBody()).getEncoding());
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL);
}
@Test
public void buildSign__withInlineEnabled__shouldSucceed() {
configureSignOnly();
configureRecipients("test@example.org");
configurePgpInline();
ArgumentCaptor<Intent> capturedApiIntent = ArgumentCaptor.forClass(Intent.class);
Intent returnIntent = new Intent();
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
when(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource.class),
nullable(OutputStream.class))).thenReturn(returnIntent);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
Intent expectedApiIntent = new Intent(OpenPgpApi.ACTION_SIGN);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID);
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.getValue());
ArgumentCaptor<MimeMessage> captor = ArgumentCaptor.forClass(MimeMessage.class);
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false));
verifyNoMoreInteractions(mockCallback);
MimeMessage message = captor.getValue();
Assert.assertEquals("message must be text/plain", "text/plain", message.getMimeType());
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL);
}
@Test
public void buildSignWithAttach__withInlineEnabled__shouldThrow() {
configureSignOnly();
configurePgpInline();
pgpMessageBuilder.setAttachments(createAttachmentList());
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(mockCallback).onMessageBuildException(any(MessagingException.class));
verifyNoMoreInteractions(mockCallback);
verifyNoMoreInteractions(openPgpApi);
}
@Test
public void buildEncryptWithAttach__withInlineEnabled__shouldThrow() {
configureEncryptAndSign();
configurePgpInline();
pgpMessageBuilder.setAttachments(createAttachmentList());
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(mockCallback).onMessageBuildException(any(MessagingException.class));
verifyNoMoreInteractions(mockCallback);
verifyNoMoreInteractions(openPgpApi);
}
@Test
public void buildSign__withNoDetachedSignatureExtra__shouldFail() {
configureSignOnly();
Intent returnIntentSigned = new Intent();
returnIntentSigned.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS);
// no OpenPgpApi.EXTRA_DETACHED_SIGNATURE!
when(openPgpApi.executeApi(any(Intent.class), any(OpenPgpDataSource.class), nullable(OutputStream.class)))
.thenReturn(returnIntentSigned);
Callback mockCallback = mock(Callback.class);
pgpMessageBuilder.buildAsync(mockCallback);
verify(mockCallback).onMessageBuildException(any(MessagingException.class));
verifyNoMoreInteractions(mockCallback);
}
private CryptoStatus createCryptoStatus() {
CryptoStatus cryptoStatus = mock(CryptoStatus.class);
when(cryptoStatus.isPgpInlineModeEnabled()).thenReturn(false);
when(cryptoStatus.isSenderPreferEncryptMutual()).thenReturn(false);
when(cryptoStatus.isEncryptSubject()).thenReturn(true);
when(cryptoStatus.getOpenPgpKeyId()).thenReturn(TEST_KEY_ID);
when(cryptoStatus.getRecipientAddresses()).thenReturn(new String[0]);
when(cryptoStatus.hasRecipients()).thenReturn(false);
when(cryptoStatus.isProviderStateOk()).thenReturn(true);
return cryptoStatus;
}
private void configureEncryptAndSign() {
when(cryptoStatus.isEncryptionEnabled()).thenReturn(true);
when(cryptoStatus.isSigningEnabled()).thenReturn(true);
}
private void configureSignOnly() {
when(cryptoStatus.isEncryptionEnabled()).thenReturn(false);
when(cryptoStatus.isSigningEnabled()).thenReturn(true);
}
private void configureCleartext() {
when(cryptoStatus.isEncryptionEnabled()).thenReturn(false);
when(cryptoStatus.isSigningEnabled()).thenReturn(false);
}
private void configurePgpInline() {
when(cryptoStatus.isPgpInlineModeEnabled()).thenReturn(true);
}
private void configureRecipients(String... recipients) {
when(cryptoStatus.hasRecipients()).thenReturn(true);
when(cryptoStatus.getRecipientAddresses()).thenReturn(recipients);
}
private static PgpMessageBuilder createDefaultPgpMessageBuilder(OpenPgpApi openPgpApi,
AutocryptOpenPgpApiInteractor autocryptOpenPgpApiInteractor, CryptoStatus cryptoStatus) {
PgpMessageBuilder builder = new PgpMessageBuilder(
MessageIdGenerator.getInstance(), BoundaryGenerator.getInstance(),
AutocryptOperations.getInstance(), autocryptOpenPgpApiInteractor, new TestCoreResourceProvider());
builder.setOpenPgpApi(openPgpApi);
builder.setCryptoStatus(cryptoStatus);
Identity identity = new Identity();
identity.setName("tester");
identity.setEmail(SENDER_EMAIL);
identity.setDescription("test identity");
identity.setSignatureUse(false);
builder.setSubject("subject")
.setSentDate(new Date())
.setHideTimeZone(false)
.setTo(new ArrayList<Address>())
.setCc(new ArrayList<Address>())
.setBcc(new ArrayList<Address>())
.setInReplyTo("inreplyto")
.setReferences("references")
.setRequestReadReceipt(false)
.setIdentity(identity)
.setMessageFormat(SimpleMessageFormat.TEXT)
.setText(TEST_MESSAGE_TEXT)
.setAttachments(new ArrayList<Attachment>())
.setSignature("signature")
.setQuoteStyle(QuoteStyle.PREFIX)
.setQuotedTextMode(QuotedTextMode.NONE)
.setQuotedText("quoted text")
.setQuotedHtmlContent(new InsertableHtmlContent())
.setReplyAfterQuote(false)
.setSignatureBeforeQuotedText(false)
.setIdentityChanged(false)
.setSignatureChanged(false)
.setCursorPosition(0)
.setMessageReference(null)
.setDraft(false);
return builder;
}
private static List<Attachment> createAttachmentList() {
Attachment attachment = mock(Attachment.class);
when(attachment.getState()).thenReturn(Attachment.LoadingState.URI_ONLY);
return Collections.singletonList(attachment);
}
private static void assertContentOfBodyPartEquals(String reason, BodyPart signatureBodyPart, byte[] expected) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
signatureBodyPart.getBody().writeTo(bos);
Assert.assertArrayEquals(reason, expected, bos.toByteArray());
} catch (IOException | MessagingException e) {
Assert.fail();
}
}
private static void assertContentOfBodyPartEquals(String reason, BodyPart signatureBodyPart, String expected) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream inputStream = MimeUtility.decodeBody(signatureBodyPart.getBody());
IOUtils.copy(inputStream, bos);
Assert.assertEquals(reason, expected, new String(bos.toByteArray(), Charsets.UTF_8));
} catch (IOException | MessagingException e) {
Assert.fail();
}
}
private static void assertIntentEqualsActionAndExtras(Intent expected, Intent actual) {
Assert.assertEquals(expected.getAction(), actual.getAction());
Bundle expectedExtras = expected.getExtras();
Bundle intentExtras = actual.getExtras();
if (expectedExtras.size() != intentExtras.size()) {
Assert.assertEquals(expectedExtras.size(), intentExtras.size());
}
for (String key : expectedExtras.keySet()) {
Object intentExtra = intentExtras.get(key);
Object expectedExtra = expectedExtras.get(key);
if (intentExtra == null) {
if (expectedExtra == null) {
continue;
}
Assert.fail("found null for an expected non-null extra: " + key);
}
if (intentExtra instanceof long[]) {
if (!Arrays.equals((long[]) intentExtra, (long[]) expectedExtra)) {
Assert.assertArrayEquals("error in " + key, (long[]) expectedExtra, (long[]) intentExtra);
}
} else {
if (!intentExtra.equals(expectedExtra)) {
Assert.assertEquals("error in " + key, expectedExtra, intentExtra);
}
}
}
}
}

View file

@ -0,0 +1,672 @@
package com.fsck.k9.message
import android.app.Activity
import android.app.PendingIntent
import android.content.Intent
import android.os.Parcelable
import com.fsck.k9.Account.QuoteStyle
import com.fsck.k9.Identity
import com.fsck.k9.RobolectricTest
import com.fsck.k9.activity.compose.ComposeCryptoStatus
import com.fsck.k9.activity.compose.RecipientPresenter.CryptoMode
import com.fsck.k9.activity.misc.Attachment
import com.fsck.k9.autocrypt.AutocryptOpenPgpApiInteractor
import com.fsck.k9.autocrypt.AutocryptOperations
import com.fsck.k9.autocrypt.AutocryptOperationsHelper.assertMessageHasAutocryptHeader
import com.fsck.k9.mail.Address
import com.fsck.k9.mail.BodyPart
import com.fsck.k9.mail.BoundaryGenerator
import com.fsck.k9.mail.MessagingException
import com.fsck.k9.mail.internet.*
import com.fsck.k9.message.MessageBuilder.Callback
import com.fsck.k9.message.quote.InsertableHtmlContent
import com.fsck.k9.view.RecipientSelectView
import com.nhaarman.mockito_kotlin.anyOrNull
import org.apache.commons.io.Charsets
import org.apache.commons.io.IOUtils
import org.apache.james.mime4j.util.MimeUtil
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.*
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState
import org.openintents.openpgp.OpenPgpError
import org.openintents.openpgp.util.OpenPgpApi
import org.openintents.openpgp.util.OpenPgpApi.OpenPgpDataSource
import org.robolectric.RuntimeEnvironment
import java.io.ByteArrayOutputStream
import java.io.IOException
import java.io.OutputStream
import java.util.*
class PgpMessageBuilderTest : RobolectricTest() {
private val defaultCryptoStatus = ComposeCryptoStatus(
OpenPgpProviderState.OK,
TEST_KEY_ID,
emptyList<RecipientSelectView.Recipient>(),
false,
false,
false,
true,
CryptoMode.NO_CHOICE
)
private val openPgpApi = mock(OpenPgpApi::class.java)
private val autocryptOpenPgpApiInteractor = mock(AutocryptOpenPgpApiInteractor::class.java)
private val pgpMessageBuilder = createDefaultPgpMessageBuilder(openPgpApi, autocryptOpenPgpApiInteractor)
@Before
@Throws(Exception::class)
fun setUp() {
BinaryTempFileBody.setTempDirectory(RuntimeEnvironment.application.cacheDir)
`when`(autocryptOpenPgpApiInteractor.getKeyMaterialForKeyId(openPgpApi, TEST_KEY_ID, SENDER_EMAIL))
.thenReturn(AUTOCRYPT_KEY_MATERIAL)
}
@Test
@Throws(MessagingException::class)
fun build__withCryptoProviderUnconfigured__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(openPgpProviderState = OpenPgpProviderState.UNCONFIGURED)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
}
@Test
@Throws(MessagingException::class)
fun build__withCryptoProviderUninitialized__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(openPgpProviderState = OpenPgpProviderState.UNINITIALIZED)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
}
@Test
@Throws(MessagingException::class)
fun build__withCryptoProviderError__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(openPgpProviderState = OpenPgpProviderState.ERROR)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
}
@Test
fun buildCleartext__withNoSigningKey__shouldBuildTrivialMessage() {
val cryptoStatus = defaultCryptoStatus.copy(openPgpKeyId = null)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
assertEquals("text/plain", message.mimeType)
}
@Test
fun buildCleartext__shouldSucceed() {
pgpMessageBuilder.setCryptoStatus(defaultCryptoStatus)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL)
}
@Test
@Throws(MessagingException::class)
fun buildSign__withNoDetachedSignatureInResult__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.SIGN_ONLY)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(any<Intent>(), any<OpenPgpDataSource>(), any<OutputStream>())).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
}
@Test
@Throws(MessagingException::class)
fun buildSign__withDetachedSignatureInResult__shouldSucceed() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.SIGN_ONLY)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val capturedApiIntent = ArgumentCaptor.forClass(Intent::class.java)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
returnIntent.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, byteArrayOf(1, 2, 3))
`when`(openPgpApi.executeApi(capturedApiIntent.capture(),
any<OpenPgpDataSource>(), anyOrNull())).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val expectedIntent = Intent(OpenPgpApi.ACTION_DETACHED_SIGN)
expectedIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID)
expectedIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
assertIntentEqualsActionAndExtras(expectedIntent, capturedApiIntent.value)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
Assert.assertEquals("message must be multipart/signed", "multipart/signed", message.mimeType)
val multipart = message.body as MimeMultipart
Assert.assertEquals("multipart/signed must consist of two parts", 2, multipart.count.toLong())
val contentBodyPart = multipart.getBodyPart(0)
Assert.assertEquals("first part must have content type text/plain",
"text/plain", MimeUtility.getHeaderParameter(contentBodyPart.contentType, null))
assertTrue("signed message body must be TextBody", contentBodyPart.body is TextBody)
Assert.assertEquals(MimeUtil.ENC_QUOTED_PRINTABLE, (contentBodyPart.body as TextBody).encoding)
assertContentOfBodyPartEquals("content must match the message text", contentBodyPart, TEST_MESSAGE_TEXT)
val signatureBodyPart = multipart.getBodyPart(1)
val contentType = signatureBodyPart.contentType
Assert.assertEquals("second part must be pgp signature", "application/pgp-signature",
MimeUtility.getHeaderParameter(contentType, null))
Assert.assertEquals("second part must be called signature.asc", "signature.asc",
MimeUtility.getHeaderParameter(contentType, "name"))
assertContentOfBodyPartEquals("content must match the supplied detached signature",
signatureBodyPart, byteArrayOf(1, 2, 3))
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL)
}
@Test
@Throws(MessagingException::class)
fun buildSign__withUserInteractionResult__shouldReturnUserInteraction() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.SIGN_ONLY)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val returnIntent = mock(Intent::class.java)
`when`(returnIntent.getIntExtra(eq(OpenPgpApi.RESULT_CODE), anyInt()))
.thenReturn(OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED)
val mockPendingIntent = mock(PendingIntent::class.java)
`when`<Parcelable>(returnIntent.getParcelableExtra<Parcelable>(eq(OpenPgpApi.RESULT_INTENT)))
.thenReturn(mockPendingIntent)
`when`(openPgpApi.executeApi(any<Intent>(), any<OpenPgpDataSource>(), any<OutputStream>())).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val captor = ArgumentCaptor.forClass(PendingIntent::class.java)
verify(mockCallback).onMessageBuildReturnPendingIntent(captor.capture(), anyInt())
verifyNoMoreInteractions(mockCallback)
val pendingIntent = captor.value
Assert.assertSame(pendingIntent, mockPendingIntent)
}
@Test
@Throws(MessagingException::class)
fun buildSign__withReturnAfterUserInteraction__shouldSucceed() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.SIGN_ONLY)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
var returnedRequestCode = 0
run {
val returnIntent = spy(Intent())
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED)
val mockPendingIntent = mock(PendingIntent::class.java)
`when`<Parcelable>(returnIntent.getParcelableExtra<Parcelable>(eq(OpenPgpApi.RESULT_INTENT)))
.thenReturn(mockPendingIntent)
`when`(openPgpApi.executeApi(any<Intent>(), any<OpenPgpDataSource>(), any<OutputStream>())).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(returnIntent).getIntExtra(eq(OpenPgpApi.RESULT_CODE), anyInt())
val piCaptor = ArgumentCaptor.forClass(PendingIntent::class.java)
val rcCaptor = ArgumentCaptor.forClass(Int::class.java)
verify(mockCallback).onMessageBuildReturnPendingIntent(piCaptor.capture(), rcCaptor.capture())
verifyNoMoreInteractions(mockCallback)
returnedRequestCode = rcCaptor.value
Assert.assertSame(mockPendingIntent, piCaptor.value)
}
run {
val returnIntent = spy(Intent())
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
val mockReturnIntent = mock(Intent::class.java)
`when`(openPgpApi.executeApi(any<Intent>(), any<OpenPgpDataSource>(), any<OutputStream>())).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.onActivityResult(returnedRequestCode, Activity.RESULT_OK, mockReturnIntent, mockCallback)
verify(openPgpApi).executeApi(same(mockReturnIntent), any<OpenPgpDataSource>(), any<OutputStream>())
verify(returnIntent).getIntExtra(eq(OpenPgpApi.RESULT_CODE), anyInt())
}
}
@Test
@Throws(MessagingException::class)
fun buildEncrypt__withoutRecipients__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.CHOICE_ENABLED)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val returnIntent = spy(Intent())
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(any(Intent::class.java), any(OpenPgpDataSource::class.java), any(OutputStream::class.java)))
.thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
}
@Test
@Throws(MessagingException::class)
fun buildEncrypt__checkGossip() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.CHOICE_ENABLED,
recipientAddresses = listOf("alice@example.org", "bob@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(any(Intent::class.java), any(OpenPgpDataSource::class.java), any(OutputStream::class.java)))
.thenReturn(returnIntent)
pgpMessageBuilder.buildAsync(mock(Callback::class.java))
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("alice@example.org"))
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("bob@example.org"))
}
@Test
@Throws(MessagingException::class)
fun buildEncrypt__checkGossip__filterBcc() {
val cryptoStatus = defaultCryptoStatus.copy(
cryptoMode = CryptoMode.CHOICE_ENABLED,
recipientAddresses = listOf("alice@example.org", "bob@example.org", "carol@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
pgpMessageBuilder.setBcc(listOf(Address("carol@example.org")))
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(any(Intent::class.java), any(OpenPgpDataSource::class.java), any(OutputStream::class.java)))
.thenReturn(returnIntent)
pgpMessageBuilder.buildAsync(mock(Callback::class.java))
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForKeyId(same(openPgpApi), eq(TEST_KEY_ID), eq(SENDER_EMAIL))
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("alice@example.org"))
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForUserId(same(openPgpApi), eq("bob@example.org"))
}
@Test
@Throws(MessagingException::class)
fun buildEncrypt__checkGossip__filterBccSingleRecipient() {
val cryptoStatus = defaultCryptoStatus.copy(
cryptoMode = CryptoMode.CHOICE_ENABLED,
isPgpInlineModeEnabled = true,
recipientAddresses = listOf("alice@example.org", "carol@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
pgpMessageBuilder.setBcc(listOf(Address("carol@example.org")))
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(any(Intent::class.java), any(OpenPgpDataSource::class.java), any(OutputStream::class.java)))
.thenReturn(returnIntent)
pgpMessageBuilder.buildAsync(mock(Callback::class.java))
verify(autocryptOpenPgpApiInteractor).getKeyMaterialForKeyId(any(OpenPgpApi::class.java), any(Long::class.java), any(String::class.java))
verifyNoMoreInteractions(autocryptOpenPgpApiInteractor)
}
@Test
@Throws(MessagingException::class)
fun buildEncrypt__shouldSucceed() {
val cryptoStatus = defaultCryptoStatus.copy(
cryptoMode = CryptoMode.CHOICE_ENABLED,
recipientAddresses = listOf("test@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val capturedApiIntent = ArgumentCaptor.forClass(Intent::class.java)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource::class.java),
any(OutputStream::class.java))).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val expectedApiIntent = Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, longArrayOf(TEST_KEY_ID))
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, cryptoStatus.recipientAddressesAsArray)
assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.value)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
Assert.assertEquals("message must be multipart/encrypted", "multipart/encrypted", message.mimeType)
val multipart = message.body as MimeMultipart
Assert.assertEquals("multipart/encrypted must consist of two parts", 2, multipart.count.toLong())
val dummyBodyPart = multipart.getBodyPart(0)
Assert.assertEquals("first part must be pgp encrypted dummy part",
"application/pgp-encrypted", dummyBodyPart.contentType)
assertContentOfBodyPartEquals("content must match the supplied detached signature",
dummyBodyPart, "Version: 1")
val encryptedBodyPart = multipart.getBodyPart(1)
Assert.assertEquals("second part must be octet-stream of encrypted data",
"application/octet-stream; name=\"encrypted.asc\"", encryptedBodyPart.contentType)
assertTrue("message body must be BinaryTempFileBody",
encryptedBodyPart.body is BinaryTempFileBody)
Assert.assertEquals(MimeUtil.ENC_7BIT, (encryptedBodyPart.body as BinaryTempFileBody).encoding)
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL)
}
@Test
@Throws(MessagingException::class)
fun buildEncrypt__withInlineEnabled__shouldSucceed() {
val cryptoStatus = defaultCryptoStatus.copy(
cryptoMode = CryptoMode.CHOICE_ENABLED,
isPgpInlineModeEnabled = true,
recipientAddresses = listOf("test@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val capturedApiIntent = ArgumentCaptor.forClass(Intent::class.java)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource::class.java),
any(OutputStream::class.java))).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val expectedApiIntent = Intent(OpenPgpApi.ACTION_SIGN_AND_ENCRYPT)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_KEY_IDS, longArrayOf(TEST_KEY_ID))
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_USER_IDS, cryptoStatus.recipientAddressesAsArray)
assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.value)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
Assert.assertEquals("text/plain", message.mimeType)
assertTrue("message body must be BinaryTempFileBody", message.body is BinaryTempFileBody)
Assert.assertEquals(MimeUtil.ENC_7BIT, (message.body as BinaryTempFileBody).encoding)
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL)
}
@Test
@Throws(MessagingException::class)
fun buildSign__withInlineEnabled__shouldSucceed() {
val cryptoStatus = defaultCryptoStatus.copy(
cryptoMode = CryptoMode.SIGN_ONLY,
isPgpInlineModeEnabled = true,
recipientAddresses = listOf("test@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val capturedApiIntent = ArgumentCaptor.forClass(Intent::class.java)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
`when`(openPgpApi.executeApi(capturedApiIntent.capture(), any(OpenPgpDataSource::class.java),
any(OutputStream::class.java))).thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val expectedApiIntent = Intent(OpenPgpApi.ACTION_SIGN)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, TEST_KEY_ID)
expectedApiIntent.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true)
assertIntentEqualsActionAndExtras(expectedApiIntent, capturedApiIntent.value)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
Assert.assertEquals("message must be text/plain", "text/plain", message.mimeType)
assertMessageHasAutocryptHeader(message, SENDER_EMAIL, false, AUTOCRYPT_KEY_MATERIAL)
}
@Test
@Throws(MessagingException::class)
fun buildSignWithAttach__withInlineEnabled__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.SIGN_ONLY, isPgpInlineModeEnabled = true)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
pgpMessageBuilder.setAttachments(listOf(Attachment.createAttachment(null, 0, null, true)))
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
verifyNoMoreInteractions(openPgpApi)
}
@Test
@Throws(MessagingException::class)
fun buildEncryptWithAttach__withInlineEnabled__shouldThrow() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.CHOICE_ENABLED, isPgpInlineModeEnabled = true)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
pgpMessageBuilder.setAttachments(listOf(Attachment.createAttachment(null, 0, null, true)))
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
verifyNoMoreInteractions(openPgpApi)
}
@Test
@Throws(MessagingException::class)
fun buildOpportunisticEncrypt__withNoKeysAndNoSignOnly__shouldNotBeSigned() {
val cryptoStatus = defaultCryptoStatus.copy(recipientAddresses = listOf("test@example.org"))
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val returnIntent = Intent()
returnIntent.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
returnIntent.putExtra(OpenPgpApi.RESULT_ERROR,
OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "Missing keys"))
`when`(openPgpApi.executeApi(any(Intent::class.java), any(OpenPgpDataSource::class.java), any(OutputStream::class.java)))
.thenReturn(returnIntent)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
val captor = ArgumentCaptor.forClass(MimeMessage::class.java)
verify(mockCallback).onMessageBuildSuccess(captor.capture(), eq(false))
verifyNoMoreInteractions(mockCallback)
val message = captor.value
Assert.assertEquals("text/plain", message.mimeType)
}
@Test
@Throws(MessagingException::class)
fun buildSign__withNoDetachedSignatureExtra__shouldFail() {
val cryptoStatus = defaultCryptoStatus.copy(cryptoMode = CryptoMode.SIGN_ONLY)
pgpMessageBuilder.setCryptoStatus(cryptoStatus)
val returnIntentSigned = Intent()
returnIntentSigned.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_SUCCESS)
// no OpenPgpApi.EXTRA_DETACHED_SIGNATURE!
`when`(openPgpApi.executeApi(any<Intent>(), any<OpenPgpDataSource>(), any<OutputStream>())).thenReturn(returnIntentSigned)
val mockCallback = mock(Callback::class.java)
pgpMessageBuilder.buildAsync(mockCallback)
verify(mockCallback).onMessageBuildException(any<MessagingException>())
verifyNoMoreInteractions(mockCallback)
}
companion object {
private val TEST_KEY_ID = 123L
private val TEST_MESSAGE_TEXT = "message text with a ☭ CCCP symbol"
private val AUTOCRYPT_KEY_MATERIAL = byteArrayOf(1, 2, 3)
private val SENDER_EMAIL = "test@example.org"
private fun createDefaultPgpMessageBuilder(openPgpApi: OpenPgpApi,
autocryptOpenPgpApiInteractor: AutocryptOpenPgpApiInteractor): PgpMessageBuilder {
val builder = PgpMessageBuilder(
RuntimeEnvironment.application, MessageIdGenerator.getInstance(), BoundaryGenerator.getInstance(),
AutocryptOperations.getInstance(), autocryptOpenPgpApiInteractor)
builder.setOpenPgpApi(openPgpApi)
val identity = Identity()
identity.name = "tester"
identity.email = SENDER_EMAIL
identity.description = "test identity"
identity.signatureUse = false
builder.setSubject("subject")
.setSentDate(Date())
.setHideTimeZone(false)
.setTo(ArrayList())
.setCc(ArrayList())
.setBcc(ArrayList())
.setInReplyTo("inreplyto")
.setReferences("references")
.setRequestReadReceipt(false)
.setIdentity(identity)
.setMessageFormat(SimpleMessageFormat.TEXT)
.setText(TEST_MESSAGE_TEXT)
.setAttachments(ArrayList())
.setSignature("signature")
.setQuoteStyle(QuoteStyle.PREFIX)
.setQuotedTextMode(QuotedTextMode.NONE)
.setQuotedText("quoted text")
.setQuotedHtmlContent(InsertableHtmlContent())
.setReplyAfterQuote(false)
.setSignatureBeforeQuotedText(false)
.setIdentityChanged(false)
.setSignatureChanged(false)
.setCursorPosition(0)
.setMessageReference(null).isDraft = false
return builder
}
private fun assertContentOfBodyPartEquals(reason: String, signatureBodyPart: BodyPart, expected: ByteArray) {
try {
val bos = ByteArrayOutputStream()
signatureBodyPart.body.writeTo(bos)
Assert.assertArrayEquals(reason, expected, bos.toByteArray())
} catch (e: IOException) {
Assert.fail()
} catch (e: MessagingException) {
Assert.fail()
}
}
private fun assertContentOfBodyPartEquals(reason: String, signatureBodyPart: BodyPart, expected: String) {
try {
val bos = ByteArrayOutputStream()
val inputStream = MimeUtility.decodeBody(signatureBodyPart.body)
IOUtils.copy(inputStream, bos)
Assert.assertEquals(reason, expected, String(bos.toByteArray(), Charsets.UTF_8))
} catch (e: IOException) {
Assert.fail()
} catch (e: MessagingException) {
Assert.fail()
}
}
private fun assertIntentEqualsActionAndExtras(expected: Intent, actual: Intent) {
Assert.assertEquals(expected.action, actual.action)
val expectedExtras = expected.extras
val intentExtras = actual.extras
if (expectedExtras!!.size() != intentExtras!!.size()) {
Assert.assertEquals(expectedExtras.size().toLong(), intentExtras.size().toLong())
}
for (key in expectedExtras.keySet()) {
val intentExtra = intentExtras.get(key)
val expectedExtra = expectedExtras.get(key)
if (intentExtra == null) {
if (expectedExtra == null) {
continue
}
Assert.fail("found null for an expected non-null extra: $key")
}
if (intentExtra is LongArray) {
if (!Arrays.equals(intentExtra, expectedExtra as LongArray)) {
Assert.assertArrayEquals("error in $key", expectedExtra, intentExtra)
}
} else {
if (intentExtra != expectedExtra) {
Assert.assertEquals("error in $key", expectedExtra, intentExtra)
}
}
}
}
}
}

View file

@ -665,7 +665,8 @@ public class MessageCompose extends K9Activity implements OnClickListener,
return null;
}
if (cryptoStatus.shouldUsePgpMessageBuilder()) {
boolean shouldUsePgpMessageBuilder = cryptoStatus.isOpenPgpConfigured();
if (shouldUsePgpMessageBuilder) {
SendErrorState maybeSendErrorState = cryptoStatus.getSendErrorStateOrNull();
if (maybeSendErrorState != null) {
recipientPresenter.showPgpSendError(maybeSendErrorState);

View file

@ -17,11 +17,11 @@ import android.os.Handler;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState;
import com.fsck.k9.activity.loader.AttachmentContentLoader;
import com.fsck.k9.activity.loader.AttachmentInfoLoader;
import com.fsck.k9.activity.misc.Attachment;
import com.fsck.k9.controller.MessageReference;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.AttachmentViewInfo;
import com.fsck.k9.mailstore.LocalMessage;

View file

@ -1,322 +0,0 @@
package com.fsck.k9.activity.compose;
import java.util.ArrayList;
import java.util.List;
import android.app.PendingIntent;
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoSpecialModeDisplayType;
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoStatusDisplayType;
import com.fsck.k9.activity.compose.RecipientPresenter.CryptoMode;
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus;
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatusType;
import com.fsck.k9.message.CryptoStatus;
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState;
import com.fsck.k9.view.RecipientSelectView.Recipient;
/** This is an immutable object which contains all relevant metadata entered
* during email composition to apply cryptographic operations before sending
* or saving as draft.
*/
public class ComposeCryptoStatus implements CryptoStatus {
private OpenPgpProviderState openPgpProviderState;
private Long openPgpKeyId;
private String[] recipientAddresses;
private boolean enablePgpInline;
private boolean preferEncryptMutual;
private boolean isReplyToEncrypted;
private boolean encryptSubject;
private CryptoMode cryptoMode;
private RecipientAutocryptStatus recipientAutocryptStatus;
public Long getOpenPgpKeyId() {
return openPgpKeyId;
}
CryptoStatusDisplayType getCryptoStatusDisplayType() {
switch (openPgpProviderState) {
case UNCONFIGURED:
return CryptoStatusDisplayType.UNCONFIGURED;
case UNINITIALIZED:
return CryptoStatusDisplayType.UNINITIALIZED;
case ERROR:
case UI_REQUIRED:
return CryptoStatusDisplayType.ERROR;
case OK:
// provider status is ok -> return value is based on cryptoMode
break;
default:
throw new AssertionError("all CryptoProviderStates must be handled!");
}
if (recipientAutocryptStatus == null) {
throw new IllegalStateException("Display type must be obtained from provider!");
}
RecipientAutocryptStatusType recipientAutocryptStatusType = recipientAutocryptStatus.type;
if (recipientAutocryptStatusType == RecipientAutocryptStatusType.ERROR) {
return CryptoStatusDisplayType.ERROR;
}
if (isEncryptionEnabled()) {
if (!recipientAutocryptStatusType.canEncrypt()) {
return CryptoStatusDisplayType.ENABLED_ERROR;
} else if (recipientAutocryptStatusType.isConfirmed()) {
return CryptoStatusDisplayType.ENABLED_TRUSTED;
} else {
return CryptoStatusDisplayType.ENABLED;
}
} else if (isSigningEnabled()) {
return CryptoStatusDisplayType.SIGN_ONLY;
} else if (recipientAutocryptStatusType.canEncrypt()) {
return CryptoStatusDisplayType.AVAILABLE;
} else {
return CryptoStatusDisplayType.UNAVAILABLE;
}
}
CryptoSpecialModeDisplayType getCryptoSpecialModeDisplayType() {
if (openPgpProviderState != OpenPgpProviderState.OK) {
return CryptoSpecialModeDisplayType.NONE;
}
if (isSignOnly() && isPgpInlineModeEnabled()) {
return CryptoSpecialModeDisplayType.SIGN_ONLY_PGP_INLINE;
}
if (isSignOnly()) {
return CryptoSpecialModeDisplayType.SIGN_ONLY;
}
if (allRecipientsCanEncrypt() && isPgpInlineModeEnabled()) {
return CryptoSpecialModeDisplayType.PGP_INLINE;
}
return CryptoSpecialModeDisplayType.NONE;
}
public boolean shouldUsePgpMessageBuilder() {
// CryptoProviderState.ERROR will be handled as an actual error, see SendErrorState
return openPgpProviderState != OpenPgpProviderState.UNCONFIGURED;
}
public boolean isEncryptionEnabled() {
if (openPgpProviderState == OpenPgpProviderState.UNCONFIGURED) {
return false;
}
boolean isExplicitlyEnabled = (cryptoMode == CryptoMode.CHOICE_ENABLED);
boolean isMutualAndNotDisabled = (cryptoMode != CryptoMode.CHOICE_DISABLED && canEncryptAndIsMutualDefault());
boolean isReplyAndNotDisabled = (cryptoMode != CryptoMode.CHOICE_DISABLED && isReplyToEncrypted());
return isExplicitlyEnabled || isMutualAndNotDisabled || isReplyAndNotDisabled;
}
public boolean isSignOnly() {
return cryptoMode == CryptoMode.SIGN_ONLY;
}
public boolean isSigningEnabled() {
return cryptoMode == CryptoMode.SIGN_ONLY || isEncryptionEnabled();
}
public boolean isPgpInlineModeEnabled() {
return enablePgpInline;
}
public boolean isProviderStateOk() {
return openPgpProviderState == OpenPgpProviderState.OK;
}
boolean allRecipientsCanEncrypt() {
return recipientAutocryptStatus != null && recipientAutocryptStatus.type.canEncrypt();
}
public boolean isUserChoice() {
return cryptoMode != CryptoMode.NO_CHOICE;
}
public String[] getRecipientAddresses() {
return recipientAddresses;
}
public boolean hasRecipients() {
return recipientAddresses.length > 0;
}
public boolean isSenderPreferEncryptMutual() {
return preferEncryptMutual;
}
private boolean isRecipientsPreferEncryptMutual() {
return recipientAutocryptStatus.type.isMutual();
}
public boolean isReplyToEncrypted() {
return isReplyToEncrypted;
}
boolean canEncryptAndIsMutualDefault() {
return allRecipientsCanEncrypt() && isSenderPreferEncryptMutual() && isRecipientsPreferEncryptMutual();
}
boolean hasAutocryptPendingIntent() {
return recipientAutocryptStatus.hasPendingIntent();
}
PendingIntent getAutocryptPendingIntent() {
return recipientAutocryptStatus.intent;
}
public boolean isEncryptSubject() {
return encryptSubject;
}
public static class ComposeCryptoStatusBuilder {
private OpenPgpProviderState openPgpProviderState;
private CryptoMode cryptoMode;
private Long openPgpKeyId;
private List<Recipient> recipients;
private Boolean enablePgpInline;
private Boolean preferEncryptMutual;
private Boolean isReplyToEncrypted;
private Boolean encryptSubject;
public ComposeCryptoStatusBuilder setOpenPgpProviderState(OpenPgpProviderState openPgpProviderState) {
this.openPgpProviderState = openPgpProviderState;
return this;
}
public ComposeCryptoStatusBuilder setCryptoMode(CryptoMode cryptoMode) {
this.cryptoMode = cryptoMode;
return this;
}
public ComposeCryptoStatusBuilder setOpenPgpKeyId(Long openPgpKeyId) {
this.openPgpKeyId = openPgpKeyId;
return this;
}
public ComposeCryptoStatusBuilder setRecipients(List<Recipient> recipients) {
this.recipients = recipients;
return this;
}
public ComposeCryptoStatusBuilder setEnablePgpInline(boolean cryptoEnableCompat) {
this.enablePgpInline = cryptoEnableCompat;
return this;
}
public ComposeCryptoStatusBuilder setPreferEncryptMutual(boolean preferEncryptMutual) {
this.preferEncryptMutual = preferEncryptMutual;
return this;
}
public ComposeCryptoStatusBuilder setIsReplyToEncrypted(boolean isReplyToEncrypted) {
this.isReplyToEncrypted = isReplyToEncrypted;
return this;
}
public ComposeCryptoStatusBuilder setEncryptSubject(boolean encryptSubject) {
this.encryptSubject = encryptSubject;
return this;
}
public ComposeCryptoStatus build() {
if (openPgpProviderState == null) {
throw new AssertionError("cryptoProviderState must be set!");
}
if (cryptoMode == null) {
throw new AssertionError("crypto mode must be set!");
}
if (recipients == null) {
throw new AssertionError("recipients must be set!");
}
if (enablePgpInline == null) {
throw new AssertionError("enablePgpInline must be set!");
}
if (preferEncryptMutual == null) {
throw new AssertionError("preferEncryptMutual must be set!");
}
if (isReplyToEncrypted == null) {
throw new AssertionError("isReplyToEncrypted must be set!");
}
if (encryptSubject == null) {
throw new AssertionError("encryptSubject must be set!");
}
ArrayList<String> recipientAddresses = new ArrayList<>();
for (Recipient recipient : recipients) {
recipientAddresses.add(recipient.address.getAddress());
}
ComposeCryptoStatus result = new ComposeCryptoStatus();
result.openPgpProviderState = openPgpProviderState;
result.cryptoMode = cryptoMode;
result.recipientAddresses = recipientAddresses.toArray(new String[0]);
result.openPgpKeyId = openPgpKeyId;
result.isReplyToEncrypted = isReplyToEncrypted;
result.enablePgpInline = enablePgpInline;
result.preferEncryptMutual = preferEncryptMutual;
result.encryptSubject = encryptSubject;
return result;
}
}
ComposeCryptoStatus withRecipientAutocryptStatus(RecipientAutocryptStatus recipientAutocryptStatusType) {
ComposeCryptoStatus result = new ComposeCryptoStatus();
result.openPgpProviderState = openPgpProviderState;
result.cryptoMode = cryptoMode;
result.recipientAddresses = recipientAddresses;
result.isReplyToEncrypted = isReplyToEncrypted;
result.openPgpKeyId = openPgpKeyId;
result.enablePgpInline = enablePgpInline;
result.preferEncryptMutual = preferEncryptMutual;
result.encryptSubject = encryptSubject;
result.recipientAutocryptStatus = recipientAutocryptStatusType;
return result;
}
public enum SendErrorState {
PROVIDER_ERROR,
KEY_CONFIG_ERROR,
ENABLED_ERROR
}
public SendErrorState getSendErrorStateOrNull() {
if (openPgpProviderState != OpenPgpProviderState.OK) {
// TODO: be more specific about this error
return SendErrorState.PROVIDER_ERROR;
}
if (openPgpKeyId == null && (isEncryptionEnabled() || isSignOnly())) {
return SendErrorState.KEY_CONFIG_ERROR;
}
if (isEncryptionEnabled() && !allRecipientsCanEncrypt()) {
return SendErrorState.ENABLED_ERROR;
}
return null;
}
enum AttachErrorState {
IS_INLINE
}
AttachErrorState getAttachErrorStateOrNull() {
if (openPgpProviderState == OpenPgpProviderState.UNCONFIGURED) {
return null;
}
if (enablePgpInline) {
return AttachErrorState.IS_INLINE;
}
return null;
}
}

View file

@ -0,0 +1,161 @@
package com.fsck.k9.activity.compose
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoSpecialModeDisplayType
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoStatusDisplayType
import com.fsck.k9.activity.compose.RecipientPresenter.CryptoMode
import com.fsck.k9.message.AutocryptStatusInteractor
import com.fsck.k9.message.AutocryptStatusInteractor.RecipientAutocryptStatus
import com.fsck.k9.message.CryptoStatus
import com.fsck.k9.view.RecipientSelectView.Recipient
import org.openintents.openpgp.OpenPgpApiManager
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpProviderState
/** This is an immutable object which contains all relevant metadata entered
* during email composition to apply cryptographic operations before sending
* or saving as draft.
*/
data class ComposeCryptoStatus(private val openPgpProviderState: OpenPgpProviderState,
private val cryptoMode: CryptoMode,
override val openPgpKeyId: Long?,
override val isPgpInlineModeEnabled: Boolean,
override val isSenderPreferEncryptMutual: Boolean,
override val isReplyToEncrypted: Boolean,
override val isEncryptSubject: Boolean,
val recipientAddresses: List<String>,
private val recipientAutocryptStatus: RecipientAutocryptStatus? = null) : CryptoStatus {
constructor(openPgpProviderState: OpenPgpProviderState,
openPgpKeyId: Long?,
recipientAddresses: List<Recipient>,
isPgpInlineModeEnabled: Boolean,
isSenderPreferEncryptMutual: Boolean,
isReplyToEncrypted: Boolean,
isEncryptSubject: Boolean,
cryptoMode: CryptoMode) : this(
openPgpProviderState, cryptoMode,
openPgpKeyId,
isPgpInlineModeEnabled, isSenderPreferEncryptMutual, isReplyToEncrypted, isEncryptSubject, recipientAddresses.map { it.address.address })
private val recipientAutocryptStatusType = recipientAutocryptStatus?.type
private val isRecipientsPreferEncryptMutual = recipientAutocryptStatus?.type?.isMutual ?: false
private val isExplicitlyEnabled = cryptoMode == CryptoMode.CHOICE_ENABLED
private val isMutualAndNotDisabled = cryptoMode != CryptoMode.CHOICE_DISABLED && canEncryptAndIsMutualDefault()
private val isReplyAndNotDisabled = cryptoMode != CryptoMode.CHOICE_DISABLED && isReplyToEncrypted
val isOpenPgpConfigured = openPgpProviderState != OpenPgpProviderState.UNCONFIGURED
override val isSignOnly = cryptoMode == CryptoMode.SIGN_ONLY
override val isEncryptionEnabled = when {
openPgpProviderState == OpenPgpProviderState.UNCONFIGURED -> false
isSignOnly -> false
isExplicitlyEnabled -> true
isMutualAndNotDisabled -> true
isReplyAndNotDisabled -> true
else -> false
}
override fun isProviderStateOk() = openPgpProviderState == OpenPgpProviderState.OK
override fun isUserChoice() = cryptoMode != CryptoMode.NO_CHOICE
override fun isSigningEnabled() = cryptoMode == CryptoMode.SIGN_ONLY || isEncryptionEnabled
val recipientAddressesAsArray = recipientAddresses.toTypedArray()
private val displayTypeFromProviderError = when (openPgpProviderState) {
OpenPgpApiManager.OpenPgpProviderState.OK -> null
OpenPgpApiManager.OpenPgpProviderState.UNCONFIGURED -> CryptoStatusDisplayType.UNCONFIGURED
OpenPgpApiManager.OpenPgpProviderState.UNINITIALIZED -> CryptoStatusDisplayType.UNINITIALIZED
OpenPgpApiManager.OpenPgpProviderState.ERROR, OpenPgpApiManager.OpenPgpProviderState.UI_REQUIRED -> CryptoStatusDisplayType.ERROR
}
private val displayTypeFromAutocryptError = when (recipientAutocryptStatusType) {
null, AutocryptStatusInteractor.RecipientAutocryptStatusType.ERROR -> CryptoStatusDisplayType.ERROR
else -> null
}
private val displayTypeFromEnabledAutocryptStatus = when {
!isEncryptionEnabled -> null
recipientAutocryptStatusType == null -> CryptoStatusDisplayType.ERROR
!recipientAutocryptStatusType.canEncrypt() -> CryptoStatusDisplayType.ENABLED_ERROR
recipientAutocryptStatusType.isConfirmed -> CryptoStatusDisplayType.ENABLED_TRUSTED
else -> CryptoStatusDisplayType.ENABLED
}
private val displayTypeFromSignOnly = when {
isSignOnly -> CryptoStatusDisplayType.SIGN_ONLY
else -> null
}
private val displayTypeFromEncryptionAvailable = when {
recipientAutocryptStatusType?.canEncrypt() == true -> CryptoStatusDisplayType.AVAILABLE
else -> null
}
val displayType =
displayTypeFromProviderError
?: displayTypeFromAutocryptError
?: displayTypeFromEnabledAutocryptStatus
?: displayTypeFromSignOnly
?: displayTypeFromEncryptionAvailable
?: CryptoStatusDisplayType.UNAVAILABLE
val specialModeDisplayType = when {
openPgpProviderState != OpenPgpProviderState.OK -> CryptoSpecialModeDisplayType.NONE
isSignOnly && isPgpInlineModeEnabled -> CryptoSpecialModeDisplayType.SIGN_ONLY_PGP_INLINE
isSignOnly -> CryptoSpecialModeDisplayType.SIGN_ONLY
allRecipientsCanEncrypt() && isPgpInlineModeEnabled -> CryptoSpecialModeDisplayType.PGP_INLINE
else -> CryptoSpecialModeDisplayType.NONE
}
val autocryptPendingIntent = recipientAutocryptStatus?.intent
val sendErrorStateOrNull = when {
openPgpProviderState != OpenPgpProviderState.OK -> SendErrorState.PROVIDER_ERROR
openPgpKeyId == null && (isEncryptionEnabled || isSignOnly) -> SendErrorState.KEY_CONFIG_ERROR
isEncryptionEnabled && !allRecipientsCanEncrypt() -> SendErrorState.ENABLED_ERROR
else -> null
}
val attachErrorStateOrNull = when {
openPgpProviderState == OpenPgpProviderState.UNCONFIGURED -> null
isPgpInlineModeEnabled -> AttachErrorState.IS_INLINE
else -> null
}
fun allRecipientsCanEncrypt() = recipientAutocryptStatus?.type?.canEncrypt() == true
fun canEncryptAndIsMutualDefault() = allRecipientsCanEncrypt() && isSenderPreferEncryptMutual && isRecipientsPreferEncryptMutual
fun hasAutocryptPendingIntent() = recipientAutocryptStatus?.hasPendingIntent() == true
override fun hasRecipients(): Boolean {
return recipientAddresses.isNotEmpty()
}
override fun getRecipientAddresses() = recipientAddresses.toTypedArray()
fun withRecipientAutocryptStatus(recipientAutocryptStatusType: RecipientAutocryptStatus) = ComposeCryptoStatus(
openPgpProviderState = openPgpProviderState,
cryptoMode = cryptoMode,
openPgpKeyId = openPgpKeyId,
isPgpInlineModeEnabled = isPgpInlineModeEnabled,
isSenderPreferEncryptMutual = isSenderPreferEncryptMutual,
isReplyToEncrypted = isReplyToEncrypted,
isEncryptSubject = isEncryptSubject,
recipientAddresses = recipientAddresses,
recipientAutocryptStatus = recipientAutocryptStatusType
)
enum class SendErrorState {
PROVIDER_ERROR,
KEY_CONFIG_ERROR,
ENABLED_ERROR
}
enum class AttachErrorState {
IS_INLINE
}
}

View file

@ -22,9 +22,7 @@ import android.view.Menu;
import com.fsck.k9.Account;
import com.fsck.k9.Identity;
import com.fsck.k9.K9;
import com.fsck.k9.ui.R;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.AttachErrorState;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.ComposeCryptoStatusBuilder;
import com.fsck.k9.activity.compose.ComposeCryptoStatus.SendErrorState;
import com.fsck.k9.activity.compose.RecipientMvpView.CryptoStatusDisplayType;
import com.fsck.k9.autocrypt.AutocryptDraftStateHeader;
@ -44,6 +42,7 @@ import com.fsck.k9.message.ComposePgpEnableByDefaultDecider;
import com.fsck.k9.message.ComposePgpInlineDecider;
import com.fsck.k9.message.MessageBuilder;
import com.fsck.k9.message.PgpMessageBuilder;
import com.fsck.k9.ui.R;
import com.fsck.k9.view.RecipientSelectView.Recipient;
import org.openintents.openpgp.OpenPgpApiManager;
import org.openintents.openpgp.OpenPgpApiManager.OpenPgpApiManagerCallback;
@ -423,16 +422,15 @@ public class RecipientPresenter {
accountCryptoKey = null;
}
final ComposeCryptoStatus composeCryptoStatus = new ComposeCryptoStatusBuilder()
.setOpenPgpProviderState(openPgpProviderState)
.setCryptoMode(currentCryptoMode)
.setEnablePgpInline(cryptoEnablePgpInline)
.setPreferEncryptMutual(account.getAutocryptPreferEncryptMutual())
.setIsReplyToEncrypted(isReplyToEncryptedMessage)
.setEncryptSubject(account.getOpenPgpEncryptSubject())
.setRecipients(getAllRecipients())
.setOpenPgpKeyId(accountCryptoKey)
.build();
final ComposeCryptoStatus composeCryptoStatus = new ComposeCryptoStatus(
openPgpProviderState,
accountCryptoKey,
getAllRecipients(),
cryptoEnablePgpInline,
account.getAutocryptPreferEncryptMutual(),
isReplyToEncryptedMessage,
account.getOpenPgpEncryptSubject(),
currentCryptoMode);
if (openPgpProviderState != OpenPgpProviderState.OK) {
cachedCryptoStatus = composeCryptoStatus;
@ -440,7 +438,7 @@ public class RecipientPresenter {
return;
}
final String[] recipientAddresses = composeCryptoStatus.getRecipientAddresses();
final String[] recipientAddresses = composeCryptoStatus.getRecipientAddressesAsArray();
new AsyncTask<Void,Void,RecipientAutocryptStatus>() {
@Override
@ -472,9 +470,9 @@ public class RecipientPresenter {
recipientMvpView.setRecipientTokensShowCryptoEnabled(cachedCryptoStatus.isEncryptionEnabled());
CryptoStatusDisplayType cryptoStatusDisplayType = cachedCryptoStatus.getCryptoStatusDisplayType();
CryptoStatusDisplayType cryptoStatusDisplayType = cachedCryptoStatus.getDisplayType();
recipientMvpView.showCryptoStatus(cryptoStatusDisplayType);
recipientMvpView.showCryptoSpecialMode(cachedCryptoStatus.getCryptoSpecialModeDisplayType());
recipientMvpView.showCryptoSpecialMode(cachedCryptoStatus.getSpecialModeDisplayType());
}
@Nullable

View file

@ -150,11 +150,11 @@ public class RecipientPresenterTest extends K9RobolectricTest {
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.UNCONFIGURED, status.getCryptoStatusDisplayType());
assertEquals(CryptoSpecialModeDisplayType.NONE, status.getCryptoSpecialModeDisplayType());
assertEquals(CryptoStatusDisplayType.UNCONFIGURED, status.getDisplayType());
assertEquals(CryptoSpecialModeDisplayType.NONE, status.getSpecialModeDisplayType());
assertNull(status.getAttachErrorStateOrNull());
assertFalse(status.isProviderStateOk());
assertFalse(status.shouldUsePgpMessageBuilder());
assertFalse(status.isOpenPgpConfigured());
}
@Test
@ -163,9 +163,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -176,9 +176,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -189,9 +189,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -202,9 +202,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -217,9 +217,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
runBackgroundTask();
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.ENABLED_ERROR, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.ENABLED_ERROR, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -232,9 +232,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
runBackgroundTask();
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.AVAILABLE, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -247,9 +247,9 @@ public class RecipientPresenterTest extends K9RobolectricTest {
runBackgroundTask();
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.ENABLED, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.ENABLED, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.shouldUsePgpMessageBuilder());
assertTrue(status.isOpenPgpConfigured());
}
@Test
@ -260,7 +260,7 @@ public class RecipientPresenterTest extends K9RobolectricTest {
runBackgroundTask();
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.SIGN_ONLY, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.SIGN_ONLY, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.isSigningEnabled());
assertTrue(status.isSignOnly());
@ -274,7 +274,7 @@ public class RecipientPresenterTest extends K9RobolectricTest {
runBackgroundTask();
ComposeCryptoStatus status = recipientPresenter.getCurrentCachedCryptoStatus();
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getCryptoStatusDisplayType());
assertEquals(CryptoStatusDisplayType.UNAVAILABLE, status.getDisplayType());
assertTrue(status.isProviderStateOk());
assertTrue(status.isPgpInlineModeEnabled());
}