diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java index 170d0f0db..b53b831bb 100644 --- a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/AbstractTwoUsersOmemoIntegrationTest.java @@ -65,8 +65,8 @@ public abstract class AbstractTwoUsersOmemoIntegrationTest extends AbstractOmemo @AfterClass public void cleanUp() { - alice.stopListeners(); - bob.stopListeners(); + alice.stopStanzaAndPEPListeners(); + bob.stopStanzaAndPEPListeners(); OmemoManagerSetupHelper.cleanUpPubSub(alice); OmemoManagerSetupHelper.cleanUpRoster(alice); OmemoManagerSetupHelper.cleanUpPubSub(bob); diff --git a/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java new file mode 100644 index 000000000..4cee07974 --- /dev/null +++ b/smack-integration-test/src/main/java/org/jivesoftware/smackx/omemo/OmemoMamDecryptionTest.java @@ -0,0 +1,77 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jivesoftware.smackx.omemo; + +import static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.XMPPException; +import org.jivesoftware.smackx.mam.MamManager; +import org.jivesoftware.smackx.mam.element.MamPrefsIQ; +import org.jivesoftware.smackx.omemo.exceptions.CryptoFailedException; +import org.jivesoftware.smackx.omemo.exceptions.UndecidedOmemoIdentityException; + +import org.igniterealtime.smack.inttest.SmackIntegrationTest; +import org.igniterealtime.smack.inttest.SmackIntegrationTestEnvironment; +import org.igniterealtime.smack.inttest.TestNotPossibleException; + +/** + * This test sends a message from Alice to Bob, while Bob has automatic decryption disabled. + * Then Bob fetches his Mam archive and decrypts the result. + */ +public class OmemoMamDecryptionTest extends AbstractTwoUsersOmemoIntegrationTest { + public OmemoMamDecryptionTest(SmackIntegrationTestEnvironment environment) + throws XMPPException.XMPPErrorException, SmackException.NotConnectedException, InterruptedException, + SmackException.NoResponseException, TestNotPossibleException { + super(environment); + MamManager bobsMamManager = MamManager.getInstanceFor(conTwo); + if (!bobsMamManager.isSupported()) { + throw new TestNotPossibleException("Test is not possible, because MAM is not supported on the server."); + } + } + + @SmackIntegrationTest + public void mamDecryptionTest() throws XMPPException.XMPPErrorException, SmackException.NotLoggedInException, + SmackException.NotConnectedException, InterruptedException, SmackException.NoResponseException, + CryptoFailedException, UndecidedOmemoIdentityException { + // Make sure, Bobs server stores messages in the archive + MamManager bobsMamManager = MamManager.getInstanceFor(bob.getConnection()); + bobsMamManager.updateArchivingPreferences(null, null, MamPrefsIQ.DefaultBehavior.always); + + // Prevent bob from automatically decrypting MAM messages. + bob.stopStanzaAndPEPListeners(); + + String body = "This message will be stored in MAM!"; + OmemoMessage.Sent encrypted = alice.encrypt(bob.getOwnJid(), body); + alice.getConnection().sendStanza(encrypted.asMessage(bob.getOwnJid())); + + int pageSize = 20; + MamManager.MamQueryResult mamQueryResult = bobsMamManager + .queryArchive(pageSize, null, null, alice.getOwnJid(), null); + + while (!mamQueryResult.mamFin.isComplete()) { + mamQueryResult = bobsMamManager.pageNext(mamQueryResult, pageSize); + } + + List decryptedMamQuery = bob.decryptMAMQueryResult(mamQueryResult); + + assertEquals(1, decryptedMamQuery.size()); + assertEquals(body, decryptedMamQuery.get(decryptedMamQuery.size() - 1).getDecrypted().getBody()); + } +} diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java index a9f1a7599..aeb803afd 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoManager.java @@ -25,7 +25,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Random; import java.util.SortedSet; import java.util.TreeMap; @@ -137,7 +136,7 @@ public final class OmemoManager extends Manager { service.registerRatchetForManager(this); // StanzaListeners - resumeStanzaListeners(); + resumeStanzaAndPEPListeners(); // Announce OMEMO support ServiceDiscoveryManager.getInstanceFor(connection).addFeature(PEP_NODE_DEVICE_LIST_NOTIFY); @@ -401,26 +400,28 @@ public final class OmemoManager extends Manager { /** * Decrypt OmemoMessages of a {@link org.jivesoftware.smackx.mam.MamManager.MamQueryResult}. - * Return a map of Stanzas and their decrypted counterparts. + * Return a list of decrypted messages. Messages that cannot be decrypted, or were not decrypted in the first place + * are left out of the list. * * Note: This method does not repair broken sessions. * * @param result MamQueryResult - * @return map of encrypted stanzas and decrypted messages. + * @return list of decrypted messages. * * @throws SmackException.NotLoggedInException if the OmemoManager is not authenticated */ - public Map decryptMAMQueryResult(MamManager.MamQueryResult result) + public List decryptMAMQueryResult(MamManager.MamQueryResult result) throws SmackException.NotLoggedInException { LoggedInOmemoManager managerGuard = new LoggedInOmemoManager(this); - HashMap decryptedMessages = new HashMap<>(); + ArrayList decryptedMessages = new ArrayList<>(); for (Forwarded forwarded : result.forwardedMessages) { Stanza stanza = forwarded.getForwardedStanza(); OmemoMessage.Received decrypted = getOmemoService().decryptStanza(stanza, managerGuard); if (decrypted != null) { - decryptedMessages.put(stanza, decrypted); + OmemoMessage.Forwarded pair = new OmemoMessage.Forwarded(decrypted, forwarded); + decryptedMessages.add(pair); } } @@ -904,9 +905,9 @@ public final class OmemoManager extends Manager { /** * Register stanza listeners needed for OMEMO. * This method is called automatically in the constructor and should only be used to restore the previous state - * after {@link #stopListeners()} was called. + * after {@link #stopStanzaAndPEPListeners()} was called. */ - public void resumeStanzaListeners() { + public void resumeStanzaAndPEPListeners() { PEPManager pepManager = PEPManager.getInstanceFor(connection()); CarbonManager carbonManager = CarbonManager.getInstanceFor(connection()); @@ -924,7 +925,7 @@ public final class OmemoManager extends Manager { /** * Remove active stanza listeners needed for OMEMO. */ - public void stopListeners() { + public void stopStanzaAndPEPListeners() { PEPManager.getInstanceFor(connection()).removePEPListener(deviceListUpdateListener); connection().removeAsyncStanzaListener(internalOmemoMessageStanzaListener); CarbonManager.getInstanceFor(connection()).removeCarbonCopyReceivedListener(internalOmemoCarbonCopyListener); diff --git a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java index 7fd935b6a..af046e93c 100644 --- a/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java +++ b/smack-omemo/src/main/java/org/jivesoftware/smackx/omemo/OmemoMessage.java @@ -210,4 +210,34 @@ public class OmemoMessage { return message == null; } } + + /** + * Binds together a {@link org.jivesoftware.smackx.forward.packet.Forwarded} object and a decrypted OmemoMessage. + */ + public static class Forwarded { + + private final org.jivesoftware.smackx.forward.packet.Forwarded forwarded; + private final Received decrypted; + + public Forwarded(Received decrypted, org.jivesoftware.smackx.forward.packet.Forwarded forwarded) { + this.decrypted = decrypted; + this.forwarded = forwarded; + } + + /** + * Return the Forwarded element, which contained the encrypted OmemoMessage. + * @return forwarded element. + */ + public org.jivesoftware.smackx.forward.packet.Forwarded getForwarded() { + return forwarded; + } + + /** + * Return the decrypted OmemoMessage. + * @return decrypted omemoMessage + */ + public Received getDecrypted() { + return decrypted; + } + } }