mirror of
https://github.com/signalapp/Signal-Server
synced 2026-04-21 10:08:04 +01:00
Break out into a multi-module project
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package org.whispersystems.textsecuregcm.tests.auth;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.Anonymous;
|
||||
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.ws.rs.WebApplicationException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class OptionalAccessTest {
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedMissingTarget() {
|
||||
try {
|
||||
OptionalAccess.verify(Optional.empty(), Optional.empty(), Optional.empty());
|
||||
throw new AssertionError("should fail");
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedMissingTargetDevice() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.isActive()).thenReturn(true);
|
||||
when(account.getDevice(eq(10))).thenReturn(Optional.empty());
|
||||
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes()));
|
||||
|
||||
try {
|
||||
OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account), "10");
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedBadTargetDevice() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.isActive()).thenReturn(true);
|
||||
when(account.getDevice(eq(10))).thenReturn(Optional.empty());
|
||||
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes()));
|
||||
|
||||
try {
|
||||
OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account), "$$");
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 422);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedBadCode() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.isActive()).thenReturn(true);
|
||||
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes()));
|
||||
|
||||
try {
|
||||
OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("5678".getBytes()))), Optional.of(account));
|
||||
throw new AssertionError("should fail");
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentifiedMissingTarget() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.isActive()).thenReturn(true);
|
||||
|
||||
try {
|
||||
OptionalAccess.verify(Optional.of(account), Optional.empty(), Optional.empty());
|
||||
throw new AssertionError("should fail");
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 404);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsolicitedBadTarget() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.isUnrestrictedUnidentifiedAccess()).thenReturn(false);
|
||||
when(account.isActive()).thenReturn(true);
|
||||
|
||||
try {
|
||||
OptionalAccess.verify(Optional.empty(), Optional.empty(), Optional.of(account));
|
||||
throw new AssertionError("shold fai");
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsolicitedGoodTarget() {
|
||||
Account account = mock(Account.class);
|
||||
Anonymous random = mock(Anonymous.class);
|
||||
when(account.isUnrestrictedUnidentifiedAccess()).thenReturn(true);
|
||||
when(account.isActive()).thenReturn(true);
|
||||
OptionalAccess.verify(Optional.empty(), Optional.of(random), Optional.of(account));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedGoodTarget() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes()));
|
||||
when(account.isActive()).thenReturn(true);
|
||||
OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedInactive() {
|
||||
Account account = mock(Account.class);
|
||||
when(account.getUnidentifiedAccessKey()).thenReturn(Optional.of("1234".getBytes()));
|
||||
when(account.isActive()).thenReturn(false);
|
||||
|
||||
try {
|
||||
OptionalAccess.verify(Optional.empty(), Optional.of(new Anonymous(Base64.encodeBytes("1234".getBytes()))), Optional.of(account));
|
||||
throw new AssertionError();
|
||||
} catch (WebApplicationException e) {
|
||||
assertEquals(e.getResponse().getStatus(), 401);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentifiedGoodTarget() {
|
||||
Account source = mock(Account.class);
|
||||
Account target = mock(Account.class);
|
||||
when(target.isActive()).thenReturn(true);
|
||||
OptionalAccess.verify(Optional.of(source), Optional.empty(), Optional.of(target));;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,510 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
|
||||
import org.whispersystems.textsecuregcm.controllers.AccountController;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLock;
|
||||
import org.whispersystems.textsecuregcm.entities.RegistrationLockFailure;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.providers.TimeProvider;
|
||||
import org.whispersystems.textsecuregcm.recaptcha.RecaptchaClient;
|
||||
import org.whispersystems.textsecuregcm.sms.SmsSender;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRule;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccountsManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Matchers.anyString;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class AccountControllerTest {
|
||||
|
||||
private static final String SENDER = "+14152222222";
|
||||
private static final String SENDER_OLD = "+14151111111";
|
||||
private static final String SENDER_PIN = "+14153333333";
|
||||
private static final String SENDER_OVER_PIN = "+14154444444";
|
||||
private static final String SENDER_OVER_PREFIX = "+14156666666";
|
||||
|
||||
private static final String ABUSIVE_HOST = "192.168.1.1";
|
||||
private static final String RESTRICTED_HOST = "192.168.1.2";
|
||||
private static final String NICE_HOST = "127.0.0.1";
|
||||
private static final String RATE_LIMITED_IP_HOST = "10.0.0.1";
|
||||
private static final String RATE_LIMITED_PREFIX_HOST = "10.0.0.2";
|
||||
private static final String RATE_LIMITED_HOST2 = "10.0.0.3";
|
||||
|
||||
private static final String VALID_CAPTCHA_TOKEN = "valid_token";
|
||||
private static final String INVALID_CAPTCHA_TOKEN = "invalid_token";
|
||||
|
||||
private PendingAccountsManager pendingAccountsManager = mock(PendingAccountsManager.class);
|
||||
private AccountsManager accountsManager = mock(AccountsManager.class );
|
||||
private AbusiveHostRules abusiveHostRules = mock(AbusiveHostRules.class );
|
||||
private RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
private RateLimiter pinLimiter = mock(RateLimiter.class );
|
||||
private RateLimiter smsVoiceIpLimiter = mock(RateLimiter.class );
|
||||
private RateLimiter smsVoicePrefixLimiter = mock(RateLimiter.class);
|
||||
private RateLimiter autoBlockLimiter = mock(RateLimiter.class);
|
||||
private SmsSender smsSender = mock(SmsSender.class );
|
||||
private DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
|
||||
private MessagesManager storedMessages = mock(MessagesManager.class );
|
||||
private TimeProvider timeProvider = mock(TimeProvider.class );
|
||||
private TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
|
||||
private Account senderPinAccount = mock(Account.class);
|
||||
private RecaptchaClient recaptchaClient = mock(RecaptchaClient.class);
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new AccountController(pendingAccountsManager,
|
||||
accountsManager,
|
||||
abusiveHostRules,
|
||||
rateLimiters,
|
||||
smsSender,
|
||||
directoryQueue,
|
||||
storedMessages,
|
||||
turnTokenGenerator,
|
||||
new HashMap<>(),
|
||||
recaptchaClient))
|
||||
.build();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
when(rateLimiters.getSmsDestinationLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getVoiceDestinationLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getVerifyLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getPinLimiter()).thenReturn(pinLimiter);
|
||||
when(rateLimiters.getSmsVoiceIpLimiter()).thenReturn(smsVoiceIpLimiter);
|
||||
when(rateLimiters.getSmsVoicePrefixLimiter()).thenReturn(smsVoicePrefixLimiter);
|
||||
when(rateLimiters.getAutoBlockLimiter()).thenReturn(autoBlockLimiter);
|
||||
|
||||
when(timeProvider.getCurrentTimeMillis()).thenReturn(System.currentTimeMillis());
|
||||
|
||||
when(senderPinAccount.getPin()).thenReturn(Optional.of("31337"));
|
||||
when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis());
|
||||
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis())));
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_OLD)).thenReturn(Optional.of(new StoredVerificationCode("1234", System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(31))));
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_PIN)).thenReturn(Optional.of(new StoredVerificationCode("333333", System.currentTimeMillis())));
|
||||
when(pendingAccountsManager.getCodeForNumber(SENDER_OVER_PIN)).thenReturn(Optional.of(new StoredVerificationCode("444444", System.currentTimeMillis())));
|
||||
|
||||
when(accountsManager.get(eq(SENDER_PIN))).thenReturn(Optional.of(senderPinAccount));
|
||||
when(accountsManager.get(eq(SENDER_OVER_PIN))).thenReturn(Optional.of(senderPinAccount));
|
||||
when(accountsManager.get(eq(SENDER))).thenReturn(Optional.empty());
|
||||
when(accountsManager.get(eq(SENDER_OLD))).thenReturn(Optional.empty());
|
||||
|
||||
when(abusiveHostRules.getAbusiveHostRulesFor(eq(ABUSIVE_HOST))).thenReturn(Collections.singletonList(new AbusiveHostRule(ABUSIVE_HOST, true, Collections.emptyList())));
|
||||
when(abusiveHostRules.getAbusiveHostRulesFor(eq(RESTRICTED_HOST))).thenReturn(Collections.singletonList(new AbusiveHostRule(RESTRICTED_HOST, false, Collections.singletonList("+123"))));
|
||||
when(abusiveHostRules.getAbusiveHostRulesFor(eq(NICE_HOST))).thenReturn(Collections.emptyList());
|
||||
|
||||
when(recaptchaClient.verify(eq(INVALID_CAPTCHA_TOKEN))).thenReturn(false);
|
||||
when(recaptchaClient.verify(eq(VALID_CAPTCHA_TOKEN))).thenReturn(true);
|
||||
|
||||
doThrow(new RateLimitExceededException(SENDER_OVER_PIN)).when(pinLimiter).validate(eq(SENDER_OVER_PIN));
|
||||
|
||||
doThrow(new RateLimitExceededException(RATE_LIMITED_PREFIX_HOST)).when(autoBlockLimiter).validate(eq(RATE_LIMITED_PREFIX_HOST));
|
||||
doThrow(new RateLimitExceededException(RATE_LIMITED_IP_HOST)).when(autoBlockLimiter).validate(eq(RATE_LIMITED_IP_HOST));
|
||||
|
||||
doThrow(new RateLimitExceededException(SENDER_OVER_PREFIX)).when(smsVoicePrefixLimiter).validate(SENDER_OVER_PREFIX.substring(0, 4+2));
|
||||
doThrow(new RateLimitExceededException(RATE_LIMITED_IP_HOST)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_IP_HOST);
|
||||
doThrow(new RateLimitExceededException(RATE_LIMITED_HOST2)).when(smsVoiceIpLimiter).validate(RATE_LIMITED_HOST2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendCode() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.request()
|
||||
.header("X-Forwarded-For", NICE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
|
||||
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(NICE_HOST));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendiOSCode() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("client", "ios")
|
||||
.request()
|
||||
.header("X-Forwarded-For", NICE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.of("ios")), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAndroidNgCode() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("client", "android-ng")
|
||||
.request()
|
||||
.header("X-Forwarded-For", NICE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.of("android-ng")), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbusiveHost() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.request()
|
||||
.header("X-Forwarded-For", ABUSIVE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(ABUSIVE_HOST));
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbusiveHostWithValidCaptcha() throws IOException {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("captcha", VALID_CAPTCHA_TOKEN)
|
||||
.request()
|
||||
.header("X-Forwarded-For", ABUSIVE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
verify(recaptchaClient).verify(eq(VALID_CAPTCHA_TOKEN));
|
||||
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.empty()), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbusiveHostWithInvalidCaptcha() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.queryParam("captcha", INVALID_CAPTCHA_TOKEN)
|
||||
.request()
|
||||
.header("X-Forwarded-For", ABUSIVE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
verify(recaptchaClient).verify(eq(INVALID_CAPTCHA_TOKEN));
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendRateLimitedHostAutoBlock() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.request()
|
||||
.header("X-Forwarded-For", RATE_LIMITED_IP_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RATE_LIMITED_IP_HOST));
|
||||
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_IP_HOST), eq("Auto-Block"));
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
|
||||
verifyNoMoreInteractions(recaptchaClient);
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendRateLimitedPrefixAutoBlock() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER_OVER_PREFIX))
|
||||
.request()
|
||||
.header("X-Forwarded-For", RATE_LIMITED_PREFIX_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RATE_LIMITED_PREFIX_HOST));
|
||||
verify(abusiveHostRules).setBlockedHost(eq(RATE_LIMITED_PREFIX_HOST), eq("Auto-Block"));
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
|
||||
verifyNoMoreInteractions(recaptchaClient);
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendRateLimitedHostNoAutoBlock() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.request()
|
||||
.header("X-Forwarded-For", RATE_LIMITED_HOST2)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RATE_LIMITED_HOST2));
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
|
||||
verifyNoMoreInteractions(recaptchaClient);
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendMultipleHost() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.request()
|
||||
.header("X-Forwarded-For", NICE_HOST + ", " + ABUSIVE_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules, times(1)).getAbusiveHostRulesFor(eq(ABUSIVE_HOST));
|
||||
|
||||
verifyNoMoreInteractions(abusiveHostRules);
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSendRestrictedHostOut() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
|
||||
.request()
|
||||
.header("X-Forwarded-For", RESTRICTED_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(402);
|
||||
|
||||
verify(abusiveHostRules).getAbusiveHostRulesFor(eq(RESTRICTED_HOST));
|
||||
verifyNoMoreInteractions(smsSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendRestrictedIn() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/sms/code/%s", "+12345678901"))
|
||||
.request()
|
||||
.header("X-Forwarded-For", RESTRICTED_HOST)
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
|
||||
verify(smsSender).deliverSmsVerification(eq("+12345678901"), eq(Optional.empty()), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyCode() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "1234"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
verify(accountsManager, times(1)).create(isA(Account.class));
|
||||
verify(directoryQueue, times(1)).deleteRegisteredUser(eq(SENDER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyCodeOld() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "1234"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OLD, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 2222, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyBadCode() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "1111"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyPin() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "333333"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, "31337"),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
verify(pinLimiter).validate(eq(SENDER_PIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyWrongPin() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "333333"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, "31338"),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(423);
|
||||
|
||||
verify(pinLimiter).validate(eq(SENDER_PIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyNoPin() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "333333"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_PIN, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(423);
|
||||
|
||||
RegistrationLockFailure failure = response.readEntity(RegistrationLockFailure.class);
|
||||
|
||||
verifyNoMoreInteractions(pinLimiter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyLimitPin() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "444444"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, "31337"),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(413);
|
||||
|
||||
verify(rateLimiter).clear(eq(SENDER_OVER_PIN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVerifyOldPin() throws Exception {
|
||||
try {
|
||||
when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7));
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/accounts/code/%s", "444444"))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(SENDER_OVER_PIN, "bar"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 3333, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
} finally {
|
||||
when(senderPinAccount.getLastSeen()).thenReturn(System.currentTimeMillis());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSetPin() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/pin/")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(new RegistrationLock("31337")));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
verify(AuthHelper.VALID_ACCOUNT).setPin(eq("31337"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetPinUnauthorized() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/pin/")
|
||||
.request()
|
||||
.put(Entity.json(new RegistrationLock("31337")));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetShortPin() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/accounts/pin/")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(new RegistrationLock("313")));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(422);
|
||||
|
||||
verify(AuthHelper.VALID_ACCOUNT, never()).setPin(anyString());
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV1;
|
||||
import org.whispersystems.textsecuregcm.controllers.AttachmentControllerV2;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV1;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentDescriptorV2;
|
||||
import org.whispersystems.textsecuregcm.entities.AttachmentUri;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class AttachmentControllerTest {
|
||||
|
||||
private static RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private static RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
|
||||
static {
|
||||
when(rateLimiters.getAttachmentLimiter()).thenReturn(rateLimiter);
|
||||
}
|
||||
|
||||
@ClassRule
|
||||
public static final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new AttachmentControllerV1(rateLimiters, "accessKey", "accessSecret", "attachment-bucket"))
|
||||
.addResource(new AttachmentControllerV2(rateLimiters, "accessKey", "accessSecret", "us-east-1", "attachmentv2-bucket"))
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testV2Form() {
|
||||
AttachmentDescriptorV2 descriptor = resources.getJerseyTest()
|
||||
.target("/v2/attachments/form/upload")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(AttachmentDescriptorV2.class);
|
||||
|
||||
assertThat(descriptor.getKey()).isEqualTo(descriptor.getAttachmentIdString());
|
||||
assertThat(descriptor.getAcl()).isEqualTo("private");
|
||||
assertThat(descriptor.getAlgorithm()).isEqualTo("AWS4-HMAC-SHA256");
|
||||
assertThat(descriptor.getAttachmentId()).isGreaterThan(0);
|
||||
assertThat(String.valueOf(descriptor.getAttachmentId())).isEqualTo(descriptor.getAttachmentIdString());
|
||||
|
||||
String[] credentialParts = descriptor.getCredential().split("/");
|
||||
|
||||
assertThat(credentialParts[0]).isEqualTo("accessKey");
|
||||
assertThat(credentialParts[2]).isEqualTo("us-east-1");
|
||||
assertThat(credentialParts[3]).isEqualTo("s3");
|
||||
assertThat(credentialParts[4]).isEqualTo("aws4_request");
|
||||
|
||||
assertThat(descriptor.getDate()).isNotBlank();
|
||||
assertThat(descriptor.getPolicy()).isNotBlank();
|
||||
assertThat(descriptor.getSignature()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceleratedPut() {
|
||||
AttachmentDescriptorV1 descriptor = resources.getJerseyTest()
|
||||
.target("/v1/attachments/")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(AttachmentDescriptorV1.class);
|
||||
|
||||
assertThat(descriptor.getLocation()).startsWith("https://attachment-bucket.s3-accelerate.amazonaws.com");
|
||||
assertThat(descriptor.getId()).isGreaterThan(0);
|
||||
assertThat(descriptor.getIdString()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnacceleratedPut() {
|
||||
AttachmentDescriptorV1 descriptor = resources.getJerseyTest()
|
||||
.target("/v1/attachments/")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER_TWO, AuthHelper.VALID_PASSWORD_TWO))
|
||||
.get(AttachmentDescriptorV1.class);
|
||||
|
||||
assertThat(descriptor.getLocation()).startsWith("https://s3.amazonaws.com");
|
||||
assertThat(descriptor.getId()).isGreaterThan(0);
|
||||
assertThat(descriptor.getIdString()).isNotBlank();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAcceleratedGet() throws MalformedURLException {
|
||||
AttachmentUri uri = resources.getJerseyTest()
|
||||
.target("/v1/attachments/1234")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(AttachmentUri.class);
|
||||
|
||||
assertThat(uri.getLocation().getHost()).isEqualTo("attachment-bucket.s3-accelerate.amazonaws.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnacceleratedGet() throws MalformedURLException {
|
||||
AttachmentUri uri = resources.getJerseyTest()
|
||||
.target("/v1/attachments/1234")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER_TWO, AuthHelper.VALID_PASSWORD_TWO))
|
||||
.get(AttachmentUri.class);
|
||||
|
||||
assertThat(uri.getLocation().getHost()).isEqualTo("s3.amazonaws.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.CertificateGenerator;
|
||||
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
||||
import org.whispersystems.textsecuregcm.controllers.CertificateController;
|
||||
import org.whispersystems.textsecuregcm.crypto.Curve;
|
||||
import org.whispersystems.textsecuregcm.entities.DeliveryCertificate;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.SenderCertificate;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.ServerCertificate;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class CertificateControllerTest {
|
||||
|
||||
private static final String caPublicKey = "BWh+UOhT1hD8bkb+MFRvb6tVqhoG8YYGCzOd7mgjo8cV";
|
||||
private static final String caPrivateKey = "EO3Mnf0kfVlVnwSaqPoQnAxhnnGL1JTdXqktCKEe9Eo=";
|
||||
|
||||
private static final String signingCertificate = "CiUIDBIhBbTz4h1My+tt+vw+TVscgUe/DeHS0W02tPWAWbTO2xc3EkD+go4bJnU0AcnFfbOLKoiBfCzouZtDYMOVi69rE7r4U9cXREEqOkUmU2WJBjykAxWPCcSTmVTYHDw7hkSp/puG";
|
||||
private static final String signingKey = "ABOxG29xrfq4E7IrW11Eg7+HBbtba9iiS0500YoBjn4=";
|
||||
|
||||
private static CertificateGenerator certificateGenerator;
|
||||
|
||||
static {
|
||||
try {
|
||||
certificateGenerator = new CertificateGenerator(Base64.decode(signingCertificate), Curve.decodePrivatePoint(Base64.decode(signingKey)), 1);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ClassRule
|
||||
public static final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new CertificateController(certificateGenerator))
|
||||
.build();
|
||||
|
||||
|
||||
@Test
|
||||
public void testValidCertificate() throws Exception {
|
||||
DeliveryCertificate certificateObject = resources.getJerseyTest()
|
||||
.target("/v1/certificate/delivery")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(DeliveryCertificate.class);
|
||||
|
||||
|
||||
SenderCertificate certificateHolder = SenderCertificate.parseFrom(certificateObject.getCertificate());
|
||||
SenderCertificate.Certificate certificate = SenderCertificate.Certificate.parseFrom(certificateHolder.getCertificate());
|
||||
|
||||
ServerCertificate serverCertificateHolder = certificate.getSigner();
|
||||
ServerCertificate.Certificate serverCertificate = ServerCertificate.Certificate.parseFrom(serverCertificateHolder.getCertificate());
|
||||
|
||||
assertTrue(Curve.verifySignature(Curve.decodePoint(serverCertificate.getKey().toByteArray(), 0), certificateHolder.getCertificate().toByteArray(), certificateHolder.getSignature().toByteArray()));
|
||||
assertTrue(Curve.verifySignature(Curve.decodePoint(Base64.decode(caPublicKey), 0), serverCertificateHolder.getCertificate().toByteArray(), serverCertificateHolder.getSignature().toByteArray()));
|
||||
|
||||
assertEquals(certificate.getSender(), AuthHelper.VALID_NUMBER);
|
||||
assertEquals(certificate.getSenderDevice(), 1L);
|
||||
assertTrue(Arrays.equals(certificate.getIdentityKey().toByteArray(), Base64.decode(AuthHelper.VALID_IDENTITY)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadAuthentication() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/certificate/delivery")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
||||
.get();
|
||||
|
||||
assertEquals(response.getStatus(), 401);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testNoAuthentication() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/certificate/delivery")
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(response.getStatus(), 401);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedAuthentication() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/certificate/delivery")
|
||||
.request()
|
||||
.header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1234".getBytes()))
|
||||
.get();
|
||||
|
||||
assertEquals(response.getStatus(), 401);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
/**
|
||||
* Copyright (C) 2014 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.controllers.DeviceController;
|
||||
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
|
||||
import org.whispersystems.textsecuregcm.entities.DeviceResponse;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.mappers.DeviceLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.*;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.VerificationCode;
|
||||
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class DeviceControllerTest {
|
||||
@Path("/v1/devices")
|
||||
static class DumbVerificationDeviceController extends DeviceController {
|
||||
public DumbVerificationDeviceController(PendingDevicesManager pendingDevices,
|
||||
AccountsManager accounts,
|
||||
MessagesManager messages,
|
||||
DirectoryQueue cdsSender,
|
||||
RateLimiters rateLimiters,
|
||||
Map<String, Integer> deviceConfiguration)
|
||||
{
|
||||
super(pendingDevices, accounts, messages, cdsSender, rateLimiters, deviceConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected VerificationCode generateVerificationCode() {
|
||||
return new VerificationCode(5678901);
|
||||
}
|
||||
}
|
||||
|
||||
private PendingDevicesManager pendingDevicesManager = mock(PendingDevicesManager.class);
|
||||
private AccountsManager accountsManager = mock(AccountsManager.class );
|
||||
private MessagesManager messagesManager = mock(MessagesManager.class);
|
||||
private DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
|
||||
private RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
private Account account = mock(Account.class );
|
||||
private Account maxedAccount = mock(Account.class);
|
||||
private Device masterDevice = mock(Device.class);
|
||||
|
||||
private Map<String, Integer> deviceConfiguration = new HashMap<String, Integer>() {{
|
||||
|
||||
}};
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addProvider(new DeviceLimitExceededExceptionMapper())
|
||||
.addResource(new DumbVerificationDeviceController(pendingDevicesManager,
|
||||
accountsManager,
|
||||
messagesManager,
|
||||
directoryQueue,
|
||||
rateLimiters,
|
||||
deviceConfiguration))
|
||||
.build();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
when(rateLimiters.getSmsDestinationLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getVoiceDestinationLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getVerifyLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getAllocateDeviceLimiter()).thenReturn(rateLimiter);
|
||||
when(rateLimiters.getVerifyDeviceLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
when(masterDevice.getId()).thenReturn(1L);
|
||||
|
||||
when(account.getNextDeviceId()).thenReturn(42L);
|
||||
when(account.getNumber()).thenReturn(AuthHelper.VALID_NUMBER);
|
||||
// when(maxedAccount.getActiveDeviceCount()).thenReturn(6);
|
||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(masterDevice));
|
||||
when(account.isActive()).thenReturn(false);
|
||||
|
||||
when(pendingDevicesManager.getCodeForNumber(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(new StoredVerificationCode("5678901", System.currentTimeMillis())));
|
||||
when(pendingDevicesManager.getCodeForNumber(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(new StoredVerificationCode("1112223", System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(31))));
|
||||
when(accountsManager.get(AuthHelper.VALID_NUMBER)).thenReturn(Optional.of(account));
|
||||
when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(maxedAccount));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validDeviceRegisterTest() throws Exception {
|
||||
VerificationCode deviceCode = resources.getJerseyTest()
|
||||
.target("/v1/devices/provisioning/code")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(VerificationCode.class);
|
||||
|
||||
assertThat(deviceCode).isEqualTo(new VerificationCode(5678901));
|
||||
|
||||
DeviceResponse response = resources.getJerseyTest()
|
||||
.target("/v1/devices/5678901")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, null),
|
||||
MediaType.APPLICATION_JSON_TYPE),
|
||||
DeviceResponse.class);
|
||||
|
||||
assertThat(response.getDeviceId()).isEqualTo(42L);
|
||||
|
||||
verify(pendingDevicesManager).remove(AuthHelper.VALID_NUMBER);
|
||||
verify(messagesManager).clear(eq(AuthHelper.VALID_NUMBER), eq(42L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidDeviceRegisterTest() throws Exception {
|
||||
VerificationCode deviceCode = resources.getJerseyTest()
|
||||
.target("/v1/devices/provisioning/code")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(VerificationCode.class);
|
||||
|
||||
assertThat(deviceCode).isEqualTo(new VerificationCode(5678901));
|
||||
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/5678902")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
|
||||
verifyNoMoreInteractions(messagesManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void oldDeviceRegisterTest() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/1112223")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER_TWO, AuthHelper.VALID_PASSWORD_TWO))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(403);
|
||||
|
||||
verifyNoMoreInteractions(messagesManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void maxDevicesTest() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/provisioning/code")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER_TWO, AuthHelper.VALID_PASSWORD_TWO))
|
||||
.get();
|
||||
|
||||
assertEquals(411, response.getStatus());
|
||||
verifyNoMoreInteractions(messagesManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void longNameTest() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/devices/5678901")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, "password1"))
|
||||
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 1234, "this is a really long name that is longer than 80 characters it's so long that it's even longer than 204 characters. that's a lot of characters. we're talking lots and lots and lots of characters. 12345678", true, true, null),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertEquals(response.getStatus(), 422);
|
||||
verifyNoMoreInteractions(messagesManager);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.auth.DirectoryCredentials;
|
||||
import org.whispersystems.textsecuregcm.auth.DirectoryCredentialsGenerator;
|
||||
import org.whispersystems.textsecuregcm.controllers.DirectoryController;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContactTokens;
|
||||
import org.whispersystems.textsecuregcm.entities.DirectoryFeedbackRequest;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
import javax.ws.rs.core.Response.Status.Family;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.anyListOf;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class DirectoryControllerTest {
|
||||
|
||||
private final RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
private final RateLimiter rateLimiter = mock(RateLimiter.class);
|
||||
private final DirectoryManager directoryManager = mock(DirectoryManager.class);
|
||||
private final DirectoryCredentialsGenerator directoryCredentialsGenerator = mock(DirectoryCredentialsGenerator.class);
|
||||
|
||||
private final DirectoryCredentials validCredentials = new DirectoryCredentials("username", "password");
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new DirectoryController(rateLimiters,
|
||||
directoryManager,
|
||||
directoryCredentialsGenerator))
|
||||
.build();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
when(rateLimiters.getContactsLimiter()).thenReturn(rateLimiter);
|
||||
when(directoryManager.get(anyListOf(byte[].class))).thenAnswer(new Answer<List<byte[]>>() {
|
||||
@Override
|
||||
public List<byte[]> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
List<byte[]> query = (List<byte[]>) invocationOnMock.getArguments()[0];
|
||||
List<byte[]> response = new LinkedList<>(query);
|
||||
response.remove(0);
|
||||
return response;
|
||||
}
|
||||
});
|
||||
when(directoryCredentialsGenerator.generateFor(eq(AuthHelper.VALID_NUMBER))).thenReturn(validCredentials);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeedbackOk() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v2/ok")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new DirectoryFeedbackRequest(Optional.of("test reason")), MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.SUCCESSFUL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotFoundFeedback() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v2/test-not-found")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new DirectoryFeedbackRequest(Optional.of("test reason")), MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatusInfo()).isEqualTo(Status.NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeedbackEmptyRequest() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v2/mismatch")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.json(""));
|
||||
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.SUCCESSFUL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeedbackNoReason() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v2/mismatch")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new DirectoryFeedbackRequest(Optional.empty()), MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.SUCCESSFUL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeedbackEmptyReason() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v2/mismatch")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new DirectoryFeedbackRequest(Optional.of("")), MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.SUCCESSFUL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFeedbackTooLargeReason() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/feedback-v2/mismatch")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new DirectoryFeedbackRequest(Optional.of(new String(new char[102400]))), MediaType.APPLICATION_JSON_TYPE));
|
||||
assertThat(response.getStatusInfo().getFamily()).isEqualTo(Family.CLIENT_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAuthToken() {
|
||||
DirectoryCredentials token =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/auth")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(DirectoryCredentials.class);
|
||||
assertThat(token.getUsername()).isEqualTo(validCredentials.getUsername());
|
||||
assertThat(token.getPassword()).isEqualTo(validCredentials.getPassword());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContactIntersection() throws Exception {
|
||||
List<String> tokens = new LinkedList<String>() {{
|
||||
add(Base64.encodeBytes("foo".getBytes()));
|
||||
add(Base64.encodeBytes("bar".getBytes()));
|
||||
add(Base64.encodeBytes("baz".getBytes()));
|
||||
}};
|
||||
|
||||
List<String> expectedResponse = new LinkedList<>(tokens);
|
||||
expectedResponse.remove(0);
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/directory/tokens/")
|
||||
.request()
|
||||
.header("Authorization",
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER,
|
||||
AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(new ClientContactTokens(tokens), MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(ClientContactTokens.class).getContacts()).isEqualTo(expectedResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,371 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
||||
import org.whispersystems.textsecuregcm.controllers.KeysController;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyCount;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyResponse;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKeyState;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class KeyControllerTest {
|
||||
|
||||
private static final String EXISTS_NUMBER = "+14152222222";
|
||||
private static String NOT_EXISTS_NUMBER = "+14152222220";
|
||||
|
||||
private static int SAMPLE_REGISTRATION_ID = 999;
|
||||
private static int SAMPLE_REGISTRATION_ID2 = 1002;
|
||||
private static int SAMPLE_REGISTRATION_ID4 = 1555;
|
||||
|
||||
private final KeyRecord SAMPLE_KEY = new KeyRecord(1, EXISTS_NUMBER, Device.MASTER_ID, 1234, "test1");
|
||||
private final KeyRecord SAMPLE_KEY2 = new KeyRecord(2, EXISTS_NUMBER, 2, 5667, "test3");
|
||||
private final KeyRecord SAMPLE_KEY3 = new KeyRecord(3, EXISTS_NUMBER, 3, 334, "test5");
|
||||
private final KeyRecord SAMPLE_KEY4 = new KeyRecord(4, EXISTS_NUMBER, 4, 336, "test6");
|
||||
|
||||
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY = new SignedPreKey( 1111, "foofoo", "sig11" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY2 = new SignedPreKey( 2222, "foobar", "sig22" );
|
||||
private final SignedPreKey SAMPLE_SIGNED_KEY3 = new SignedPreKey( 3333, "barfoo", "sig33" );
|
||||
private final SignedPreKey VALID_DEVICE_SIGNED_KEY = new SignedPreKey(89898, "zoofarb", "sigvalid");
|
||||
|
||||
private final Keys keys = mock(Keys.class );
|
||||
private final AccountsManager accounts = mock(AccountsManager.class);
|
||||
private final DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
|
||||
private final Account existsAccount = mock(Account.class );
|
||||
|
||||
private RateLimiters rateLimiters = mock(RateLimiters.class);
|
||||
private RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new KeysController(rateLimiters, keys, accounts, directoryQueue))
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
final Device sampleDevice = mock(Device.class);
|
||||
final Device sampleDevice2 = mock(Device.class);
|
||||
final Device sampleDevice3 = mock(Device.class);
|
||||
final Device sampleDevice4 = mock(Device.class);
|
||||
|
||||
Set<Device> allDevices = new HashSet<Device>() {{
|
||||
add(sampleDevice);
|
||||
add(sampleDevice2);
|
||||
add(sampleDevice3);
|
||||
add(sampleDevice4);
|
||||
}};
|
||||
|
||||
when(sampleDevice.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID);
|
||||
when(sampleDevice2.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2);
|
||||
when(sampleDevice3.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID2);
|
||||
when(sampleDevice4.getRegistrationId()).thenReturn(SAMPLE_REGISTRATION_ID4);
|
||||
when(sampleDevice.isActive()).thenReturn(true);
|
||||
when(sampleDevice2.isActive()).thenReturn(true);
|
||||
when(sampleDevice3.isActive()).thenReturn(false);
|
||||
when(sampleDevice4.isActive()).thenReturn(true);
|
||||
when(sampleDevice.getSignedPreKey()).thenReturn(SAMPLE_SIGNED_KEY);
|
||||
when(sampleDevice2.getSignedPreKey()).thenReturn(SAMPLE_SIGNED_KEY2);
|
||||
when(sampleDevice3.getSignedPreKey()).thenReturn(SAMPLE_SIGNED_KEY3);
|
||||
when(sampleDevice4.getSignedPreKey()).thenReturn(null);
|
||||
when(sampleDevice.getId()).thenReturn(1L);
|
||||
when(sampleDevice2.getId()).thenReturn(2L);
|
||||
when(sampleDevice3.getId()).thenReturn(3L);
|
||||
when(sampleDevice4.getId()).thenReturn(4L);
|
||||
|
||||
when(existsAccount.getDevice(1L)).thenReturn(Optional.of(sampleDevice));
|
||||
when(existsAccount.getDevice(2L)).thenReturn(Optional.of(sampleDevice2));
|
||||
when(existsAccount.getDevice(3L)).thenReturn(Optional.of(sampleDevice3));
|
||||
when(existsAccount.getDevice(4L)).thenReturn(Optional.of(sampleDevice4));
|
||||
when(existsAccount.getDevice(22L)).thenReturn(Optional.<Device>empty());
|
||||
when(existsAccount.getDevices()).thenReturn(allDevices);
|
||||
when(existsAccount.isActive()).thenReturn(true);
|
||||
when(existsAccount.getIdentityKey()).thenReturn("existsidentitykey");
|
||||
when(existsAccount.getNumber()).thenReturn(EXISTS_NUMBER);
|
||||
when(existsAccount.getUnidentifiedAccessKey()).thenReturn(Optional.of("1337".getBytes()));
|
||||
|
||||
when(accounts.get(EXISTS_NUMBER)).thenReturn(Optional.of(existsAccount));
|
||||
when(accounts.get(NOT_EXISTS_NUMBER)).thenReturn(Optional.<Account>empty());
|
||||
|
||||
when(rateLimiters.getPreKeysLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
List<KeyRecord> singleDevice = new LinkedList<>();
|
||||
singleDevice.add(SAMPLE_KEY);
|
||||
when(keys.get(eq(EXISTS_NUMBER), eq(1L))).thenReturn(singleDevice);
|
||||
|
||||
when(keys.get(eq(NOT_EXISTS_NUMBER), eq(1L))).thenReturn(new LinkedList<>());
|
||||
|
||||
List<KeyRecord> multiDevice = new LinkedList<>();
|
||||
multiDevice.add(SAMPLE_KEY);
|
||||
multiDevice.add(SAMPLE_KEY2);
|
||||
multiDevice.add(SAMPLE_KEY3);
|
||||
multiDevice.add(SAMPLE_KEY4);
|
||||
when(keys.get(EXISTS_NUMBER)).thenReturn(multiDevice);
|
||||
|
||||
when(keys.getCount(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(5);
|
||||
|
||||
when(AuthHelper.VALID_DEVICE.getSignedPreKey()).thenReturn(VALID_DEVICE_SIGNED_KEY);
|
||||
when(AuthHelper.VALID_ACCOUNT.getIdentityKey()).thenReturn(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validKeyStatusTestV2() throws Exception {
|
||||
PreKeyCount result = resources.getJerseyTest()
|
||||
.target("/v2/keys")
|
||||
.request()
|
||||
.header("Authorization",
|
||||
AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyCount.class);
|
||||
|
||||
assertThat(result.getCount()).isEqualTo(4);
|
||||
|
||||
verify(keys).getCount(eq(AuthHelper.VALID_NUMBER), eq(1L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSignedPreKeyV2() throws Exception {
|
||||
SignedPreKey result = resources.getJerseyTest()
|
||||
.target("/v2/keys/signed")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(SignedPreKey.class);
|
||||
|
||||
assertThat(result.getSignature()).isEqualTo(VALID_DEVICE_SIGNED_KEY.getSignature());
|
||||
assertThat(result.getKeyId()).isEqualTo(VALID_DEVICE_SIGNED_KEY.getKeyId());
|
||||
assertThat(result.getPublicKey()).isEqualTo(VALID_DEVICE_SIGNED_KEY.getPublicKey());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putSignedPreKeyV2() throws Exception {
|
||||
SignedPreKey test = new SignedPreKey(9999, "fooozzz", "baaarzzz");
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v2/keys/signed")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(test, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
verify(AuthHelper.VALID_DEVICE).setSignedPreKey(eq(test));
|
||||
verify(accounts).update(eq(AuthHelper.VALID_ACCOUNT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validSingleRequestTestV2() throws Exception {
|
||||
PreKeyResponse result = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyResponse.class);
|
||||
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
assertThat(result.getDevicesCount()).isEqualTo(1);
|
||||
assertThat(result.getDevice(1).getPreKey().getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(result.getDevice(1).getPreKey().getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(result.getDevice(1).getSignedPreKey()).isEqualTo(existsAccount.getDevice(1).get().getSignedPreKey());
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER), eq(1L));
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnidentifiedRequest() throws Exception {
|
||||
PreKeyResponse result = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("1337".getBytes()))
|
||||
.get(PreKeyResponse.class);
|
||||
|
||||
assertThat(result.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
assertThat(result.getDevicesCount()).isEqualTo(1);
|
||||
assertThat(result.getDevice(1).getPreKey().getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(result.getDevice(1).getPreKey().getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(result.getDevice(1).getSignedPreKey()).isEqualTo(existsAccount.getDevice(1).get().getSignedPreKey());
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER), eq(1L));
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnauthorizedUnidentifiedRequest() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header(OptionalAccess.UNIDENTIFIED, AuthHelper.getUnidentifiedAccessHeader("9999".getBytes()))
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMalformedUnidentifiedRequest() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header(OptionalAccess.UNIDENTIFIED, "$$$$$$$$$")
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void validMultiRequestTestV2() throws Exception {
|
||||
PreKeyResponse results = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/*", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(PreKeyResponse.class);
|
||||
|
||||
assertThat(results.getDevicesCount()).isEqualTo(3);
|
||||
assertThat(results.getIdentityKey()).isEqualTo(existsAccount.getIdentityKey());
|
||||
|
||||
PreKey signedPreKey = results.getDevice(1).getSignedPreKey();
|
||||
PreKey preKey = results.getDevice(1).getPreKey();
|
||||
long registrationId = results.getDevice(1).getRegistrationId();
|
||||
long deviceId = results.getDevice(1).getDeviceId();
|
||||
|
||||
assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY.getKeyId());
|
||||
assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY.getPublicKey());
|
||||
assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID);
|
||||
assertThat(signedPreKey.getKeyId()).isEqualTo(SAMPLE_SIGNED_KEY.getKeyId());
|
||||
assertThat(signedPreKey.getPublicKey()).isEqualTo(SAMPLE_SIGNED_KEY.getPublicKey());
|
||||
assertThat(deviceId).isEqualTo(1);
|
||||
|
||||
signedPreKey = results.getDevice(2).getSignedPreKey();
|
||||
preKey = results.getDevice(2).getPreKey();
|
||||
registrationId = results.getDevice(2).getRegistrationId();
|
||||
deviceId = results.getDevice(2).getDeviceId();
|
||||
|
||||
assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY2.getKeyId());
|
||||
assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY2.getPublicKey());
|
||||
assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID2);
|
||||
assertThat(signedPreKey.getKeyId()).isEqualTo(SAMPLE_SIGNED_KEY2.getKeyId());
|
||||
assertThat(signedPreKey.getPublicKey()).isEqualTo(SAMPLE_SIGNED_KEY2.getPublicKey());
|
||||
assertThat(deviceId).isEqualTo(2);
|
||||
|
||||
signedPreKey = results.getDevice(4).getSignedPreKey();
|
||||
preKey = results.getDevice(4).getPreKey();
|
||||
registrationId = results.getDevice(4).getRegistrationId();
|
||||
deviceId = results.getDevice(4).getDeviceId();
|
||||
|
||||
assertThat(preKey.getKeyId()).isEqualTo(SAMPLE_KEY4.getKeyId());
|
||||
assertThat(preKey.getPublicKey()).isEqualTo(SAMPLE_KEY4.getPublicKey());
|
||||
assertThat(registrationId).isEqualTo(SAMPLE_REGISTRATION_ID4);
|
||||
assertThat(signedPreKey).isNull();
|
||||
assertThat(deviceId).isEqualTo(4);
|
||||
|
||||
verify(keys).get(eq(EXISTS_NUMBER));
|
||||
verifyNoMoreInteractions(keys);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidRequestTestV2() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s", NOT_EXISTS_NUMBER))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void anotherInvalidRequestTestV2() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/22", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(404);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unauthorizedRequestTestV2() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(401);
|
||||
|
||||
response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v2/keys/%s/1", EXISTS_NUMBER))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatusInfo().getStatusCode()).isEqualTo(401);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void putKeysTestV2() throws Exception {
|
||||
final PreKey preKey = new PreKey(31337, "foobar");
|
||||
final SignedPreKey signedPreKey = new SignedPreKey(31338, "foobaz", "myvalidsig");
|
||||
final String identityKey = "barbar";
|
||||
|
||||
List<PreKey> preKeys = new LinkedList<PreKey>() {{
|
||||
add(preKey);
|
||||
}};
|
||||
|
||||
PreKeyState preKeyState = new PreKeyState(identityKey, signedPreKey, preKeys);
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v2/keys")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(preKeyState, MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(204);
|
||||
|
||||
ArgumentCaptor<List> listCaptor = ArgumentCaptor.forClass(List.class);
|
||||
verify(keys).store(eq(AuthHelper.VALID_NUMBER), eq(1L), listCaptor.capture());
|
||||
|
||||
List<PreKey> capturedList = listCaptor.getValue();
|
||||
assertThat(capturedList.size()).isEqualTo(1);
|
||||
assertThat(capturedList.get(0).getKeyId()).isEqualTo(31337);
|
||||
assertThat(capturedList.get(0).getPublicKey()).isEqualTo("foobar");
|
||||
|
||||
verify(AuthHelper.VALID_ACCOUNT).setIdentityKey(eq("barbar"));
|
||||
verify(AuthHelper.VALID_DEVICE).setSignedPreKey(eq(signedPreKey));
|
||||
verify(accounts).update(AuthHelper.VALID_ACCOUNT);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,332 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.whispersystems.textsecuregcm.auth.OptionalAccess;
|
||||
import org.whispersystems.textsecuregcm.controllers.MessageController;
|
||||
import org.whispersystems.textsecuregcm.entities.IncomingMessageList;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
||||
import org.whispersystems.textsecuregcm.entities.MismatchedDevices;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.entities.StaleDevices;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import javax.ws.rs.client.Entity;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.asJson;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture;
|
||||
|
||||
public class MessageControllerTest {
|
||||
|
||||
private static final String SINGLE_DEVICE_RECIPIENT = "+14151111111";
|
||||
private static final String MULTI_DEVICE_RECIPIENT = "+14152222222";
|
||||
|
||||
private final PushSender pushSender = mock(PushSender.class );
|
||||
private final ReceiptSender receiptSender = mock(ReceiptSender.class);
|
||||
private final AccountsManager accountsManager = mock(AccountsManager.class );
|
||||
private final MessagesManager messagesManager = mock(MessagesManager.class);
|
||||
private final RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private final RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
private final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class);
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new MessageController(rateLimiters, pushSender, receiptSender, accountsManager,
|
||||
messagesManager, apnFallbackManager))
|
||||
.build();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
Set<Device> singleDeviceList = new HashSet<Device>() {{
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 111, new SignedPreKey(333, "baz", "boop"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true));
|
||||
}};
|
||||
|
||||
Set<Device> multiDeviceList = new HashSet<Device>() {{
|
||||
add(new Device(1, null, "foo", "bar", "baz", "isgcm", null, null, false, 222, new SignedPreKey(111, "foo", "bar"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true));
|
||||
add(new Device(2, null, "foo", "bar", "baz", "isgcm", null, null, false, 333, new SignedPreKey(222, "oof", "rab"), System.currentTimeMillis(), System.currentTimeMillis(), "Test", true));
|
||||
add(new Device(3, null, "foo", "bar", "baz", "isgcm", null, null, false, 444, null, System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31), System.currentTimeMillis(), "Test", true));
|
||||
}};
|
||||
|
||||
Account singleDeviceAccount = new Account(SINGLE_DEVICE_RECIPIENT, singleDeviceList, "1234".getBytes());
|
||||
Account multiDeviceAccount = new Account(MULTI_DEVICE_RECIPIENT, multiDeviceList, "1234".getBytes());
|
||||
|
||||
when(accountsManager.get(eq(SINGLE_DEVICE_RECIPIENT))).thenReturn(Optional.of(singleDeviceAccount));
|
||||
when(accountsManager.get(eq(MULTI_DEVICE_RECIPIENT))).thenReturn(Optional.of(multiDeviceAccount));
|
||||
|
||||
when(rateLimiters.getMessagesLimiter()).thenReturn(rateLimiter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testSingleDeviceCurrent() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response", response.getStatus(), is(equalTo(200)));
|
||||
|
||||
ArgumentCaptor<Envelope> captor = ArgumentCaptor.forClass(Envelope.class);
|
||||
verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), captor.capture(), eq(false));
|
||||
|
||||
assertTrue(captor.getValue().hasSource());
|
||||
assertTrue(captor.getValue().hasSourceDevice());
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testSingleDeviceCurrentUnidentified() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.header(OptionalAccess.UNIDENTIFIED, Base64.encodeBytes("1234".getBytes()))
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response", response.getStatus(), is(equalTo(200)));
|
||||
|
||||
ArgumentCaptor<Envelope> captor = ArgumentCaptor.forClass(Envelope.class);
|
||||
verify(pushSender, times(1)).sendMessage(any(Account.class), any(Device.class), captor.capture(), eq(false));
|
||||
|
||||
assertFalse(captor.getValue().hasSource());
|
||||
assertFalse(captor.getValue().hasSourceDevice());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public synchronized void testSendBadAuth() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s", SINGLE_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response", response.getStatus(), is(equalTo(401)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testMultiDeviceMissing() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_single_device.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(409)));
|
||||
|
||||
assertThat("Good Response Body",
|
||||
asJson(response.readEntity(MismatchedDevices.class)),
|
||||
is(equalTo(jsonFixture("fixtures/missing_device_response.json"))));
|
||||
|
||||
verifyNoMoreInteractions(pushSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testMultiDeviceExtra() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_extra_device.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(409)));
|
||||
|
||||
assertThat("Good Response Body",
|
||||
asJson(response.readEntity(MismatchedDevices.class)),
|
||||
is(equalTo(jsonFixture("fixtures/missing_device_response2.json"))));
|
||||
|
||||
verifyNoMoreInteractions(pushSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testMultiDevice() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_multi_device.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(200)));
|
||||
|
||||
verify(pushSender, times(2)).sendMessage(any(Account.class), any(Device.class), any(Envelope.class), eq(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testRegistrationIdMismatch() throws Exception {
|
||||
Response response =
|
||||
resources.getJerseyTest().target(String.format("/v1/messages/%s", MULTI_DEVICE_RECIPIENT))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.put(Entity.entity(mapper.readValue(jsonFixture("fixtures/current_message_registration_id.json"), IncomingMessageList.class),
|
||||
MediaType.APPLICATION_JSON_TYPE));
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(410)));
|
||||
|
||||
assertThat("Good Response Body",
|
||||
asJson(response.readEntity(StaleDevices.class)),
|
||||
is(equalTo(jsonFixture("fixtures/mismatched_registration_id.json"))));
|
||||
|
||||
verifyNoMoreInteractions(pushSender);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testGetMessages() throws Exception {
|
||||
|
||||
final long timestampOne = 313377;
|
||||
final long timestampTwo = 313388;
|
||||
|
||||
final UUID uuidOne = UUID.randomUUID();
|
||||
|
||||
List<OutgoingMessageEntity> messages = new LinkedList<OutgoingMessageEntity>() {{
|
||||
add(new OutgoingMessageEntity(1L, false, uuidOne, Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null, 0));
|
||||
add(new OutgoingMessageEntity(2L, false, null, Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null, 0));
|
||||
}};
|
||||
|
||||
OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false);
|
||||
|
||||
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(messagesList);
|
||||
|
||||
OutgoingMessageEntityList response =
|
||||
resources.getJerseyTest().target("/v1/messages/")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.accept(MediaType.APPLICATION_JSON_TYPE)
|
||||
.get(OutgoingMessageEntityList.class);
|
||||
|
||||
|
||||
assertEquals(response.getMessages().size(), 2);
|
||||
|
||||
assertEquals(response.getMessages().get(0).getId(), 0);
|
||||
assertEquals(response.getMessages().get(1).getId(), 0);
|
||||
|
||||
assertEquals(response.getMessages().get(0).getTimestamp(), timestampOne);
|
||||
assertEquals(response.getMessages().get(1).getTimestamp(), timestampTwo);
|
||||
|
||||
assertEquals(response.getMessages().get(0).getGuid(), uuidOne);
|
||||
assertEquals(response.getMessages().get(1).getGuid(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testGetMessagesBadAuth() throws Exception {
|
||||
final long timestampOne = 313377;
|
||||
final long timestampTwo = 313388;
|
||||
|
||||
List<OutgoingMessageEntity> messages = new LinkedList<OutgoingMessageEntity>() {{
|
||||
add(new OutgoingMessageEntity(1L, false, UUID.randomUUID(), Envelope.Type.CIPHERTEXT_VALUE, null, timestampOne, "+14152222222", 2, "hi there".getBytes(), null, 0));
|
||||
add(new OutgoingMessageEntity(2L, false, UUID.randomUUID(), Envelope.Type.RECEIPT_VALUE, null, timestampTwo, "+14152222222", 2, null, null, 0));
|
||||
}};
|
||||
|
||||
OutgoingMessageEntityList messagesList = new OutgoingMessageEntityList(messages, false);
|
||||
|
||||
when(messagesManager.getMessagesForDevice(eq(AuthHelper.VALID_NUMBER), eq(1L))).thenReturn(messagesList);
|
||||
|
||||
Response response =
|
||||
resources.getJerseyTest().target("/v1/messages/")
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.INVALID_PASSWORD))
|
||||
.accept(MediaType.APPLICATION_JSON_TYPE)
|
||||
.get();
|
||||
|
||||
assertThat("Unauthorized response", response.getStatus(), is(equalTo(401)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public synchronized void testDeleteMessages() throws Exception {
|
||||
long timestamp = System.currentTimeMillis();
|
||||
|
||||
when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31337))
|
||||
.thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, null,
|
||||
Envelope.Type.CIPHERTEXT_VALUE,
|
||||
null, timestamp,
|
||||
"+14152222222", 1, "hi".getBytes(), null, 0)));
|
||||
|
||||
when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31338))
|
||||
.thenReturn(Optional.of(new OutgoingMessageEntity(31337L, true, null,
|
||||
Envelope.Type.RECEIPT_VALUE,
|
||||
null, System.currentTimeMillis(),
|
||||
"+14152222222", 1, null, null, 0)));
|
||||
|
||||
|
||||
when(messagesManager.delete(AuthHelper.VALID_NUMBER, 1, "+14152222222", 31339))
|
||||
.thenReturn(Optional.empty());
|
||||
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s/%d", "+14152222222", 31337))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.delete();
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(204)));
|
||||
verify(receiptSender).sendReceipt(any(Account.class), eq("+14152222222"), eq(timestamp));
|
||||
|
||||
response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s/%d", "+14152222222", 31338))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.delete();
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(204)));
|
||||
verifyNoMoreInteractions(receiptSender);
|
||||
|
||||
response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/messages/%s/%d", "+14152222222", 31339))
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.delete();
|
||||
|
||||
assertThat("Good Response Code", response.getStatus(), is(equalTo(204)));
|
||||
verifyNoMoreInteractions(receiptSender);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.ProfilesConfiguration;
|
||||
import org.whispersystems.textsecuregcm.controllers.ProfileController;
|
||||
import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
|
||||
import org.whispersystems.textsecuregcm.entities.Profile;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiter;
|
||||
import org.whispersystems.textsecuregcm.limits.RateLimiters;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Java6Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class ProfileControllerTest {
|
||||
|
||||
private static AccountsManager accountsManager = mock(AccountsManager.class );
|
||||
private static RateLimiters rateLimiters = mock(RateLimiters.class );
|
||||
private static RateLimiter rateLimiter = mock(RateLimiter.class );
|
||||
private static ProfilesConfiguration configuration = mock(ProfilesConfiguration.class);
|
||||
|
||||
static {
|
||||
when(configuration.getAccessKey()).thenReturn("accessKey");
|
||||
when(configuration.getAccessSecret()).thenReturn("accessSecret");
|
||||
when(configuration.getRegion()).thenReturn("us-east-1");
|
||||
when(configuration.getBucket()).thenReturn("profile-bucket");
|
||||
}
|
||||
|
||||
@ClassRule
|
||||
public static final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new ProfileController(rateLimiters,
|
||||
accountsManager,
|
||||
configuration))
|
||||
.build();
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
when(rateLimiters.getProfileLimiter()).thenReturn(rateLimiter);
|
||||
|
||||
Account profileAccount = mock(Account.class);
|
||||
|
||||
when(profileAccount.getIdentityKey()).thenReturn("bar");
|
||||
when(profileAccount.getProfileName()).thenReturn("baz");
|
||||
when(profileAccount.getAvatar()).thenReturn("profiles/bang");
|
||||
when(profileAccount.getAvatarDigest()).thenReturn("buh");
|
||||
when(profileAccount.isActive()).thenReturn(true);
|
||||
|
||||
when(accountsManager.get(AuthHelper.VALID_NUMBER_TWO)).thenReturn(Optional.of(profileAccount));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testProfileGet() throws RateLimitExceededException {
|
||||
Profile profile= resources.getJerseyTest()
|
||||
.target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO)
|
||||
.request()
|
||||
.header("Authorization", AuthHelper.getAuthHeader(AuthHelper.VALID_NUMBER, AuthHelper.VALID_PASSWORD))
|
||||
.get(Profile.class);
|
||||
|
||||
assertThat(profile.getIdentityKey()).isEqualTo("bar");
|
||||
assertThat(profile.getName()).isEqualTo("baz");
|
||||
assertThat(profile.getAvatar()).isEqualTo("profiles/bang");
|
||||
|
||||
verify(accountsManager, times(1)).get(AuthHelper.VALID_NUMBER_TWO);
|
||||
verify(rateLimiters, times(1)).getProfileLimiter();
|
||||
verify(rateLimiter, times(1)).validate(AuthHelper.VALID_NUMBER);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProfileGetUnauthorized() throws Exception {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target("/v1/profile/" + AuthHelper.VALID_NUMBER_TWO)
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(401);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.TransparentDataController;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.PublicAccount;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static junit.framework.TestCase.*;
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.asJson;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.jsonFixture;
|
||||
|
||||
public class TransparentDataControllerTest {
|
||||
|
||||
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
private final Map<String, String> indexMap = new HashMap<>();
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new TransparentDataController(accountsManager, indexMap))
|
||||
.build();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
Account accountOne = new Account("+14151231111", Collections.singleton(new Device(1, "foo", "bar", "salt", "keykey", "gcm-id", "apn-id", "voipapn-id", true, 1234, new SignedPreKey(5, "public-signed", "signtture-signed"), 31337, 31336, "CoolClient", true)), new byte[16]);
|
||||
Account accountTwo = new Account("+14151232222", Collections.singleton(new Device(1, "2foo", "2bar", "2salt", "2keykey", "2gcm-id", "2apn-id", "2voipapn-id", true, 1234, new SignedPreKey(5, "public-signed", "signtture-signed"), 31337, 31336, "CoolClient", true)), new byte[16]);
|
||||
|
||||
accountOne.setProfileName("OneProfileName");
|
||||
accountOne.setIdentityKey("identity_key_value");
|
||||
accountTwo.setProfileName("TwoProfileName");
|
||||
accountTwo.setIdentityKey("different_identity_key_value");
|
||||
|
||||
|
||||
indexMap.put("1", "+14151231111");
|
||||
indexMap.put("2", "+14151232222");
|
||||
|
||||
when(accountsManager.get(eq("+14151231111"))).thenReturn(Optional.of(accountOne));
|
||||
when(accountsManager.get(eq("+14151232222"))).thenReturn(Optional.of(accountTwo));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountOne() throws IOException {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/transparency/account/%s", "1"))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
Account result = response.readEntity(PublicAccount.class);
|
||||
|
||||
assertTrue(result.getPin().isPresent());
|
||||
assertEquals("******", result.getPin().get());
|
||||
assertNull(result.getNumber());
|
||||
assertEquals("OneProfileName", result.getProfileName());
|
||||
|
||||
assertThat("Account serialization works",
|
||||
asJson(result),
|
||||
is(equalTo(jsonFixture("fixtures/transparent_account.json"))));
|
||||
|
||||
verify(accountsManager, times(1)).get(eq("+14151231111"));
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountTwo() throws IOException {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/transparency/account/%s", "2"))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(200, response.getStatus());
|
||||
|
||||
Account result = response.readEntity(PublicAccount.class);
|
||||
|
||||
assertTrue(result.getPin().isPresent());
|
||||
assertEquals("******", result.getPin().get());
|
||||
assertNull(result.getNumber());
|
||||
assertEquals("TwoProfileName", result.getProfileName());
|
||||
|
||||
assertThat("Account serialization works 2",
|
||||
asJson(result),
|
||||
is(equalTo(jsonFixture("fixtures/transparent_account2.json"))));
|
||||
|
||||
verify(accountsManager, times(1)).get(eq("+14151232222"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountMissing() {
|
||||
Response response = resources.getJerseyTest()
|
||||
.target(String.format("/v1/transparency/account/%s", "3"))
|
||||
.request()
|
||||
.get();
|
||||
|
||||
assertEquals(404, response.getStatus());
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.whispersystems.textsecuregcm.tests.controllers;
|
||||
|
||||
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.controllers.VoiceVerificationController;
|
||||
import org.whispersystems.textsecuregcm.mappers.RateLimitExceededExceptionMapper;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.tests.util.AuthHelper;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import javax.ws.rs.core.Response;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
import io.dropwizard.auth.AuthValueFactoryProvider;
|
||||
import io.dropwizard.testing.FixtureHelpers;
|
||||
import io.dropwizard.testing.junit.ResourceTestRule;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class VoiceVerificationControllerTest {
|
||||
|
||||
@Rule
|
||||
public final ResourceTestRule resources = ResourceTestRule.builder()
|
||||
.addProvider(AuthHelper.getAuthFilter())
|
||||
.addProvider(new AuthValueFactoryProvider.Binder<>(Account.class))
|
||||
.addProvider(new RateLimitExceededExceptionMapper())
|
||||
.setMapper(SystemMapper.getMapper())
|
||||
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
|
||||
.addResource(new VoiceVerificationController("https://foo.com/bar",
|
||||
new HashSet<>(Arrays.asList("pt-BR", "ru"))))
|
||||
.build();
|
||||
|
||||
@Test
|
||||
public void testTwimlLocale() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/voice/description/123456")
|
||||
.queryParam("l", "pt-BR")
|
||||
.request()
|
||||
.post(null);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(String.class)).isXmlEqualTo(FixtureHelpers.fixture("fixtures/voice_verification_pt_br.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwimlSplitLocale() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/voice/description/123456")
|
||||
.queryParam("l", "ru-RU")
|
||||
.request()
|
||||
.post(null);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(String.class)).isXmlEqualTo(FixtureHelpers.fixture("fixtures/voice_verification_ru.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwimlUnsupportedLocale() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/voice/description/123456")
|
||||
.queryParam("l", "es-MX")
|
||||
.request()
|
||||
.post(null);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(String.class)).isXmlEqualTo(FixtureHelpers.fixture("fixtures/voice_verification_en_us.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwimlMissingLocale() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/voice/description/123456")
|
||||
.request()
|
||||
.post(null);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(String.class)).isXmlEqualTo(FixtureHelpers.fixture("fixtures/voice_verification_en_us.xml"));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testTwimlMalformedCode() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/voice/description/1234...56")
|
||||
.request()
|
||||
.post(null);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(200);
|
||||
assertThat(response.readEntity(String.class)).isXmlEqualTo(FixtureHelpers.fixture("fixtures/voice_verification_en_us.xml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTwimlBadCodeLength() {
|
||||
Response response =
|
||||
resources.getJerseyTest()
|
||||
.target("/v1/voice/description/1234567")
|
||||
.request()
|
||||
.post(null);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(400);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.whispersystems.textsecuregcm.tests.entities;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*;
|
||||
|
||||
public class ClientContactTest {
|
||||
|
||||
@Test
|
||||
public void serializeToJSON() throws Exception {
|
||||
byte[] token = Util.getContactToken("+14152222222");
|
||||
ClientContact contact = new ClientContact(token, null, false, false);
|
||||
ClientContact contactWithRelay = new ClientContact(token, "whisper", false, false);
|
||||
ClientContact contactWithRelayVox = new ClientContact(token, "whisper", true, false);
|
||||
ClientContact contactWithRelayVid = new ClientContact(token, "whisper", true, true);
|
||||
|
||||
assertThat("Basic Contact Serialization works",
|
||||
asJson(contact),
|
||||
is(equalTo(jsonFixture("fixtures/contact.json"))));
|
||||
|
||||
assertThat("Contact Relay Serialization works",
|
||||
asJson(contactWithRelay),
|
||||
is(equalTo(jsonFixture("fixtures/contact.relay.json"))));
|
||||
|
||||
assertThat("Contact Relay Vox Serializaton works",
|
||||
asJson(contactWithRelayVox),
|
||||
is(equalTo(jsonFixture("fixtures/contact.relay.voice.json"))));
|
||||
|
||||
assertThat("Contact Relay Video Serializaton works",
|
||||
asJson(contactWithRelayVid),
|
||||
is(equalTo(jsonFixture("fixtures/contact.relay.video.json"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserializeFromJSON() throws Exception {
|
||||
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
|
||||
"whisper", false, false);
|
||||
|
||||
assertThat("a ClientContact can be deserialized from JSON",
|
||||
fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class),
|
||||
is(contact));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.whispersystems.textsecuregcm.tests.entities;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.equalTo;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.whispersystems.textsecuregcm.tests.util.JsonHelpers.*;
|
||||
|
||||
public class PreKeyTest {
|
||||
|
||||
@Test
|
||||
public void deserializeFromJSONV() throws Exception {
|
||||
ClientContact contact = new ClientContact(Util.getContactToken("+14152222222"),
|
||||
"whisper", false, false);
|
||||
|
||||
assertThat("a ClientContact can be deserialized from JSON",
|
||||
fromJson(jsonFixture("fixtures/contact.relay.json"), ClientContact.class),
|
||||
is(contact));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serializeToJSONV2() throws Exception {
|
||||
PreKey preKey = new PreKey(1234, "test");
|
||||
|
||||
assertThat("PreKeyV2 Serialization works",
|
||||
asJson(preKey),
|
||||
is(equalTo(jsonFixture("fixtures/prekey_v2.json"))));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package org.whispersystems.textsecuregcm.tests.http;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.configuration.RetryConfiguration;
|
||||
import org.whispersystems.textsecuregcm.http.FaultTolerantHttpClient;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.util.concurrent.CompletionException;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class FaultTolerantHttpClientTest {
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort().dynamicHttpsPort());
|
||||
|
||||
@Test
|
||||
public void testSimpleGet() {
|
||||
wireMockRule.stubFor(get(urlEqualTo("/ping"))
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "text/plain")
|
||||
.withBody("Pong!")));
|
||||
|
||||
|
||||
FaultTolerantHttpClient client = FaultTolerantHttpClient.newBuilder()
|
||||
.withCircuitBreaker(new CircuitBreakerConfiguration())
|
||||
.withRetry(new RetryConfiguration())
|
||||
.withExecutor(Executors.newSingleThreadExecutor())
|
||||
.withName("test")
|
||||
.withVersion(HttpClient.Version.HTTP_2)
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:" + wireMockRule.port() + "/ping"))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
|
||||
assertThat(response.statusCode()).isEqualTo(200);
|
||||
assertThat(response.body()).isEqualTo("Pong!");
|
||||
|
||||
verify(1, getRequestedFor(urlEqualTo("/ping")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetryGet() {
|
||||
wireMockRule.stubFor(get(urlEqualTo("/failure"))
|
||||
.willReturn(aResponse()
|
||||
.withStatus(500)
|
||||
.withHeader("Content-Type", "text/plain")
|
||||
.withBody("Pong!")));
|
||||
|
||||
FaultTolerantHttpClient client = FaultTolerantHttpClient.newBuilder()
|
||||
.withCircuitBreaker(new CircuitBreakerConfiguration())
|
||||
.withRetry(new RetryConfiguration())
|
||||
.withExecutor(Executors.newSingleThreadExecutor())
|
||||
.withName("test")
|
||||
.withVersion(HttpClient.Version.HTTP_2)
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:" + wireMockRule.port() + "/failure"))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
|
||||
assertThat(response.statusCode()).isEqualTo(500);
|
||||
assertThat(response.body()).isEqualTo("Pong!");
|
||||
|
||||
verify(3, getRequestedFor(urlEqualTo("/failure")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNetworkFailureCircuitBreaker() throws InterruptedException {
|
||||
CircuitBreakerConfiguration circuitBreakerConfiguration = new CircuitBreakerConfiguration();
|
||||
circuitBreakerConfiguration.setRingBufferSizeInClosedState(2);
|
||||
circuitBreakerConfiguration.setRingBufferSizeInHalfOpenState(1);
|
||||
circuitBreakerConfiguration.setFailureRateThreshold(50);
|
||||
circuitBreakerConfiguration.setWaitDurationInOpenStateInSeconds(1);
|
||||
|
||||
FaultTolerantHttpClient client = FaultTolerantHttpClient.newBuilder()
|
||||
.withCircuitBreaker(circuitBreakerConfiguration)
|
||||
.withRetry(new RetryConfiguration())
|
||||
.withExecutor(Executors.newSingleThreadExecutor())
|
||||
.withName("test")
|
||||
.withVersion(HttpClient.Version.HTTP_2)
|
||||
.build();
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create("http://localhost:" + 39873 + "/failure"))
|
||||
.GET()
|
||||
.build();
|
||||
|
||||
try {
|
||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
throw new AssertionError("Should have failed!");
|
||||
} catch (CompletionException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(IOException.class);
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
throw new AssertionError("Should have failed!");
|
||||
} catch (CompletionException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(IOException.class);
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
throw new AssertionError("Should have failed!");
|
||||
} catch (CompletionException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(CircuitBreakerOpenException.class);
|
||||
// good
|
||||
}
|
||||
|
||||
Thread.sleep(1001);
|
||||
|
||||
try {
|
||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
throw new AssertionError("Should have failed!");
|
||||
} catch (CompletionException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(IOException.class);
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).join();
|
||||
throw new AssertionError("Should have failed!");
|
||||
} catch (CompletionException e) {
|
||||
assertThat(e.getCause()).isInstanceOf(CircuitBreakerOpenException.class);
|
||||
// good
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package org.whispersystems.textsecuregcm.tests.limits;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.limits.LeakyBucket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class LeakyBucketTest {
|
||||
|
||||
@Test
|
||||
public void testFull() {
|
||||
LeakyBucket leakyBucket = new LeakyBucket(2, 1.0 / 2.0);
|
||||
|
||||
assertTrue(leakyBucket.add(1));
|
||||
assertTrue(leakyBucket.add(1));
|
||||
assertFalse(leakyBucket.add(1));
|
||||
|
||||
leakyBucket = new LeakyBucket(2, 1.0 / 2.0);
|
||||
|
||||
assertTrue(leakyBucket.add(2));
|
||||
assertFalse(leakyBucket.add(1));
|
||||
assertFalse(leakyBucket.add(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLapseRate() throws IOException {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String serialized = "{\"bucketSize\":2,\"leakRatePerMillis\":8.333333333333334E-6,\"spaceRemaining\":0,\"lastUpdateTimeMillis\":" + (System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(2)) + "}";
|
||||
|
||||
LeakyBucket leakyBucket = LeakyBucket.fromSerialized(mapper, serialized);
|
||||
assertTrue(leakyBucket.add(1));
|
||||
|
||||
String serializedAgain = leakyBucket.serialize(mapper);
|
||||
LeakyBucket leakyBucketAgain = LeakyBucket.fromSerialized(mapper, serializedAgain);
|
||||
|
||||
assertFalse(leakyBucketAgain.add(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLapseShort() throws Exception {
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
String serialized = "{\"bucketSize\":2,\"leakRatePerMillis\":8.333333333333334E-6,\"spaceRemaining\":0,\"lastUpdateTimeMillis\":" + (System.currentTimeMillis() - TimeUnit.MINUTES.toMillis(1)) + "}";
|
||||
|
||||
LeakyBucket leakyBucket = LeakyBucket.fromSerialized(mapper, serialized);
|
||||
assertFalse(leakyBucket.add(1));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,423 @@
|
||||
package org.whispersystems.textsecuregcm.tests.push;
|
||||
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.turo.pushy.apns.ApnsClient;
|
||||
import com.turo.pushy.apns.ApnsPushNotification;
|
||||
import com.turo.pushy.apns.DeliveryPriority;
|
||||
import com.turo.pushy.apns.PushNotificationResponse;
|
||||
import com.turo.pushy.apns.util.SimpleApnsPushNotification;
|
||||
import com.turo.pushy.apns.util.concurrent.PushNotificationFuture;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.push.APNSender;
|
||||
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
|
||||
import org.whispersystems.textsecuregcm.push.ApnMessage;
|
||||
import org.whispersystems.textsecuregcm.push.RetryingApnsClient;
|
||||
import org.whispersystems.textsecuregcm.push.RetryingApnsClient.ApnResult;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import io.netty.util.concurrent.DefaultEventExecutor;
|
||||
import io.netty.util.concurrent.DefaultPromise;
|
||||
import io.netty.util.concurrent.EventExecutor;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class APNSenderTest {
|
||||
|
||||
private static final String DESTINATION_NUMBER = "+14151231234";
|
||||
private static final String DESTINATION_APN_ID = "foo";
|
||||
|
||||
private final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
|
||||
private final Account destinationAccount = mock(Account.class);
|
||||
private final Device destinationDevice = mock(Device.class);
|
||||
private final ApnFallbackManager fallbackManager = mock(ApnFallbackManager.class);
|
||||
|
||||
private final DefaultEventExecutor executor = new DefaultEventExecutor();
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
|
||||
when(destinationDevice.getApnId()).thenReturn(DESTINATION_APN_ID);
|
||||
when(accountsManager.get(DESTINATION_NUMBER)).thenReturn(Optional.of(destinationAccount));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendVoip() throws Exception {
|
||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
|
||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
when(response.isAccepted()).thenReturn(true);
|
||||
|
||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(executor, invocationOnMock.getArgument(0), response));
|
||||
|
||||
RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient);
|
||||
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, true);
|
||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
|
||||
apnSender.setApnFallbackManager(fallbackManager);
|
||||
ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
ApnResult apnResult = sendFuture.get();
|
||||
|
||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
|
||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(ApnMessage.MAX_EXPIRATION));
|
||||
assertThat(notification.getValue().getPayload()).isEqualTo(ApnMessage.APN_PAYLOAD);
|
||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
assertThat(notification.getValue().getTopic()).isEqualTo("foo.voip");
|
||||
|
||||
assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.SUCCESS);
|
||||
|
||||
verifyNoMoreInteractions(apnsClient);
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
verifyNoMoreInteractions(fallbackManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendApns() throws Exception {
|
||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
|
||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
when(response.isAccepted()).thenReturn(true);
|
||||
|
||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(executor, invocationOnMock.getArgument(0), response));
|
||||
|
||||
RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient);
|
||||
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, false);
|
||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
apnSender.setApnFallbackManager(fallbackManager);
|
||||
|
||||
ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
ApnResult apnResult = sendFuture.get();
|
||||
|
||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
|
||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(ApnMessage.MAX_EXPIRATION));
|
||||
assertThat(notification.getValue().getPayload()).isEqualTo(ApnMessage.APN_PAYLOAD);
|
||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
assertThat(notification.getValue().getTopic()).isEqualTo("foo");
|
||||
|
||||
assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.SUCCESS);
|
||||
|
||||
verifyNoMoreInteractions(apnsClient);
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
verifyNoMoreInteractions(fallbackManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisteredUser() throws Exception {
|
||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
|
||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
when(response.isAccepted()).thenReturn(false);
|
||||
when(response.getRejectionReason()).thenReturn("Unregistered");
|
||||
|
||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(executor, invocationOnMock.getArgument(0), response));
|
||||
|
||||
|
||||
RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient);
|
||||
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, true);
|
||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
apnSender.setApnFallbackManager(fallbackManager);
|
||||
|
||||
when(destinationDevice.getApnId()).thenReturn(DESTINATION_APN_ID);
|
||||
when(destinationDevice.getPushTimestamp()).thenReturn(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(11));
|
||||
|
||||
ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
ApnResult apnResult = sendFuture.get();
|
||||
|
||||
Thread.sleep(1000); // =(
|
||||
|
||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
|
||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(ApnMessage.MAX_EXPIRATION));
|
||||
assertThat(notification.getValue().getPayload()).isEqualTo(ApnMessage.APN_PAYLOAD);
|
||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
|
||||
assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.NO_SUCH_USER);
|
||||
|
||||
verifyNoMoreInteractions(apnsClient);
|
||||
verify(accountsManager, times(1)).get(eq(DESTINATION_NUMBER));
|
||||
verify(destinationAccount, times(1)).getDevice(1);
|
||||
verify(destinationDevice, times(1)).getApnId();
|
||||
verify(destinationDevice, times(1)).getPushTimestamp();
|
||||
// verify(destinationDevice, times(1)).setApnId(eq((String)null));
|
||||
// verify(destinationDevice, times(1)).setVoipApnId(eq((String)null));
|
||||
// verify(destinationDevice, times(1)).setFetchesMessages(eq(false));
|
||||
// verify(accountsManager, times(1)).update(eq(destinationAccount));
|
||||
verify(fallbackManager, times(1)).cancel(eq(destinationAccount), eq(destinationDevice));
|
||||
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
verifyNoMoreInteractions(fallbackManager);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testVoipUnregisteredUser() throws Exception {
|
||||
// ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
//
|
||||
// PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
// when(response.isAccepted()).thenReturn(false);
|
||||
// when(response.getRejectionReason()).thenReturn("Unregistered");
|
||||
//
|
||||
// DefaultPromise<PushNotificationResponse<SimpleApnsPushNotification>> result = new DefaultPromise<>(executor);
|
||||
// result.setSuccess(response);
|
||||
//
|
||||
// when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
// .thenReturn(result);
|
||||
//
|
||||
// RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient, 10);
|
||||
// ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, "message", true, 30);
|
||||
// APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
// apnSender.setApnFallbackManager(fallbackManager);
|
||||
//
|
||||
// when(destinationDevice.getApnId()).thenReturn("baz");
|
||||
// when(destinationDevice.getVoipApnId()).thenReturn(DESTINATION_APN_ID);
|
||||
// when(destinationDevice.getPushTimestamp()).thenReturn(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(11));
|
||||
//
|
||||
// ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
// ApnResult apnResult = sendFuture.get();
|
||||
//
|
||||
// Thread.sleep(1000); // =(
|
||||
//
|
||||
// ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
// verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
//
|
||||
// assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
// assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(30));
|
||||
// assertThat(notification.getValue().getPayload()).isEqualTo("message");
|
||||
// assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
//
|
||||
// assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.NO_SUCH_USER);
|
||||
//
|
||||
// verifyNoMoreInteractions(apnsClient);
|
||||
// verify(accountsManager, times(1)).get(eq(DESTINATION_NUMBER));
|
||||
// verify(destinationAccount, times(1)).getDevice(1);
|
||||
// verify(destinationDevice, times(1)).getApnId();
|
||||
// verify(destinationDevice, times(1)).getVoipApnId();
|
||||
// verify(destinationDevice, times(1)).getPushTimestamp();
|
||||
// verify(destinationDevice, times(1)).setApnId(eq((String)null));
|
||||
// verify(destinationDevice, times(1)).setVoipApnId(eq((String)null));
|
||||
// verify(destinationDevice, times(1)).setFetchesMessages(eq(false));
|
||||
// verify(accountsManager, times(1)).update(eq(destinationAccount));
|
||||
// verify(fallbackManager, times(1)).cancel(eq(new WebsocketAddress(DESTINATION_NUMBER, 1)));
|
||||
//
|
||||
// verifyNoMoreInteractions(accountsManager);
|
||||
// verifyNoMoreInteractions(fallbackManager);
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testRecentUnregisteredUser() throws Exception {
|
||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
|
||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
when(response.isAccepted()).thenReturn(false);
|
||||
when(response.getRejectionReason()).thenReturn("Unregistered");
|
||||
|
||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(executor, invocationOnMock.getArgument(0), response));
|
||||
|
||||
RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient);
|
||||
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, true);
|
||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
apnSender.setApnFallbackManager(fallbackManager);
|
||||
|
||||
when(destinationDevice.getApnId()).thenReturn(DESTINATION_APN_ID);
|
||||
when(destinationDevice.getPushTimestamp()).thenReturn(System.currentTimeMillis());
|
||||
|
||||
ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
ApnResult apnResult = sendFuture.get();
|
||||
|
||||
Thread.sleep(1000); // =(
|
||||
|
||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
|
||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(ApnMessage.MAX_EXPIRATION));
|
||||
assertThat(notification.getValue().getPayload()).isEqualTo(ApnMessage.APN_PAYLOAD);
|
||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
|
||||
assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.NO_SUCH_USER);
|
||||
|
||||
verifyNoMoreInteractions(apnsClient);
|
||||
verify(accountsManager, times(1)).get(eq(DESTINATION_NUMBER));
|
||||
verify(destinationAccount, times(1)).getDevice(1);
|
||||
verify(destinationDevice, times(1)).getApnId();
|
||||
verify(destinationDevice, times(1)).getPushTimestamp();
|
||||
|
||||
verifyNoMoreInteractions(destinationDevice);
|
||||
verifyNoMoreInteractions(destinationAccount);
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
verifyNoMoreInteractions(fallbackManager);
|
||||
}
|
||||
|
||||
// @Test
|
||||
// public void testUnregisteredUserOldApnId() throws Exception {
|
||||
// ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
//
|
||||
// PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
// when(response.isAccepted()).thenReturn(false);
|
||||
// when(response.getRejectionReason()).thenReturn("Unregistered");
|
||||
//
|
||||
// DefaultPromise<PushNotificationResponse<SimpleApnsPushNotification>> result = new DefaultPromise<>(executor);
|
||||
// result.setSuccess(response);
|
||||
//
|
||||
// when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
// .thenReturn(result);
|
||||
//
|
||||
// RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient, 10);
|
||||
// ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, "message", true, 30);
|
||||
// APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
// apnSender.setApnFallbackManager(fallbackManager);
|
||||
//
|
||||
// when(destinationDevice.getApnId()).thenReturn("baz");
|
||||
// when(destinationDevice.getPushTimestamp()).thenReturn(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(12));
|
||||
//
|
||||
// ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
// ApnResult apnResult = sendFuture.get();
|
||||
//
|
||||
// Thread.sleep(1000); // =(
|
||||
//
|
||||
// ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
// verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
//
|
||||
// assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
// assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(30));
|
||||
// assertThat(notification.getValue().getPayload()).isEqualTo("message");
|
||||
// assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
//
|
||||
// assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.NO_SUCH_USER);
|
||||
//
|
||||
// verifyNoMoreInteractions(apnsClient);
|
||||
// verify(accountsManager, times(1)).get(eq(DESTINATION_NUMBER));
|
||||
// verify(destinationAccount, times(1)).getDevice(1);
|
||||
// verify(destinationDevice, times(2)).getApnId();
|
||||
// verify(destinationDevice, times(2)).getVoipApnId();
|
||||
//
|
||||
// verifyNoMoreInteractions(destinationDevice);
|
||||
// verifyNoMoreInteractions(destinationAccount);
|
||||
// verifyNoMoreInteractions(accountsManager);
|
||||
// verifyNoMoreInteractions(fallbackManager);
|
||||
// }
|
||||
|
||||
@Test
|
||||
public void testGenericFailure() throws Exception {
|
||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
|
||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
when(response.isAccepted()).thenReturn(false);
|
||||
when(response.getRejectionReason()).thenReturn("BadTopic");
|
||||
|
||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(executor, invocationOnMock.getArgument(0), response));
|
||||
|
||||
RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient);
|
||||
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, true);
|
||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
apnSender.setApnFallbackManager(fallbackManager);
|
||||
|
||||
ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
ApnResult apnResult = sendFuture.get();
|
||||
|
||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
|
||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(ApnMessage.MAX_EXPIRATION));
|
||||
assertThat(notification.getValue().getPayload()).isEqualTo(ApnMessage.APN_PAYLOAD);
|
||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
|
||||
assertThat(apnResult.getStatus()).isEqualTo(ApnResult.Status.GENERIC_FAILURE);
|
||||
|
||||
verifyNoMoreInteractions(apnsClient);
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
verifyNoMoreInteractions(fallbackManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailure() throws Exception {
|
||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
||||
|
||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||
when(response.isAccepted()).thenReturn(true);
|
||||
|
||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(executor, invocationOnMock.getArgument(0), new Exception("lost connection")));
|
||||
|
||||
RetryingApnsClient retryingApnsClient = new RetryingApnsClient(apnsClient);
|
||||
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, true);
|
||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), accountsManager, retryingApnsClient, "foo", false);
|
||||
apnSender.setApnFallbackManager(fallbackManager);
|
||||
|
||||
ListenableFuture<ApnResult> sendFuture = apnSender.sendMessage(message);
|
||||
|
||||
try {
|
||||
sendFuture.get();
|
||||
throw new AssertionError();
|
||||
} catch (InterruptedException e) {
|
||||
throw new AssertionError(e);
|
||||
} catch (ExecutionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||
verify(apnsClient, times(1)).sendNotification(notification.capture());
|
||||
|
||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
||||
assertThat(notification.getValue().getExpiration()).isEqualTo(new Date(ApnMessage.MAX_EXPIRATION));
|
||||
assertThat(notification.getValue().getPayload()).isEqualTo(ApnMessage.APN_PAYLOAD);
|
||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||
|
||||
verifyNoMoreInteractions(apnsClient);
|
||||
verifyNoMoreInteractions(accountsManager);
|
||||
verifyNoMoreInteractions(fallbackManager);
|
||||
}
|
||||
|
||||
private static class MockPushNotificationFuture <P extends ApnsPushNotification, V> extends DefaultPromise<V> implements PushNotificationFuture<P, V> {
|
||||
|
||||
private final P pushNotification;
|
||||
|
||||
MockPushNotificationFuture(final EventExecutor eventExecutor, final P pushNotification) {
|
||||
super(eventExecutor);
|
||||
this.pushNotification = pushNotification;
|
||||
}
|
||||
|
||||
MockPushNotificationFuture(final EventExecutor eventExecutor, final P pushNotification, final V response) {
|
||||
super(eventExecutor);
|
||||
this.pushNotification = pushNotification;
|
||||
setSuccess(response);
|
||||
}
|
||||
|
||||
MockPushNotificationFuture(final EventExecutor eventExecutor, final P pushNotification, final Exception exception) {
|
||||
super(eventExecutor);
|
||||
this.pushNotification = pushNotification;
|
||||
setFailure(exception);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public P getPushNotification() {
|
||||
return pushNotification;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package org.whispersystems.textsecuregcm.tests.push;
|
||||
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Matchers;
|
||||
import org.whispersystems.gcm.server.Message;
|
||||
import org.whispersystems.gcm.server.Result;
|
||||
import org.whispersystems.gcm.server.Sender;
|
||||
import org.whispersystems.textsecuregcm.push.GCMSender;
|
||||
import org.whispersystems.textsecuregcm.push.GcmMessage;
|
||||
import org.whispersystems.textsecuregcm.sqs.DirectoryQueue;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class GCMSenderTest {
|
||||
|
||||
@Test
|
||||
public void testSendMessage() {
|
||||
AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
Sender sender = mock(Sender.class );
|
||||
Result successResult = mock(Result.class );
|
||||
DirectoryQueue directoryQueue = mock(DirectoryQueue.class );
|
||||
SynchronousExecutorService executorService = new SynchronousExecutorService();
|
||||
|
||||
when(successResult.isInvalidRegistrationId()).thenReturn(false);
|
||||
when(successResult.isUnregistered()).thenReturn(false);
|
||||
when(successResult.hasCanonicalRegistrationId()).thenReturn(false);
|
||||
when(successResult.isSuccess()).thenReturn(true);
|
||||
|
||||
GcmMessage message = new GcmMessage("foo", "+12223334444", 1, false);
|
||||
GCMSender gcmSender = new GCMSender(accountsManager, sender, directoryQueue, executorService);
|
||||
|
||||
SettableFuture<Result> successFuture = SettableFuture.create();
|
||||
successFuture.set(successResult);
|
||||
|
||||
when(sender.send(any(Message.class), Matchers.anyObject())).thenReturn(successFuture);
|
||||
when(successResult.getContext()).thenReturn(message);
|
||||
|
||||
gcmSender.sendMessage(message);
|
||||
|
||||
verify(sender, times(1)).send(any(Message.class), eq(message));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendError() {
|
||||
String destinationNumber = "+12223334444";
|
||||
String gcmId = "foo";
|
||||
|
||||
AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
Sender sender = mock(Sender.class );
|
||||
Result invalidResult = mock(Result.class );
|
||||
DirectoryQueue directoryQueue = mock(DirectoryQueue.class );
|
||||
SynchronousExecutorService executorService = new SynchronousExecutorService();
|
||||
|
||||
Account destinationAccount = mock(Account.class);
|
||||
Device destinationDevice = mock(Device.class );
|
||||
|
||||
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
|
||||
when(accountsManager.get(destinationNumber)).thenReturn(Optional.of(destinationAccount));
|
||||
when(destinationDevice.getGcmId()).thenReturn(gcmId);
|
||||
|
||||
when(invalidResult.isInvalidRegistrationId()).thenReturn(true);
|
||||
when(invalidResult.isUnregistered()).thenReturn(false);
|
||||
when(invalidResult.hasCanonicalRegistrationId()).thenReturn(false);
|
||||
when(invalidResult.isSuccess()).thenReturn(true);
|
||||
|
||||
GcmMessage message = new GcmMessage(gcmId, destinationNumber, 1, false);
|
||||
GCMSender gcmSender = new GCMSender(accountsManager, sender, directoryQueue, executorService);
|
||||
|
||||
SettableFuture<Result> invalidFuture = SettableFuture.create();
|
||||
invalidFuture.set(invalidResult);
|
||||
|
||||
when(sender.send(any(Message.class), Matchers.anyObject())).thenReturn(invalidFuture);
|
||||
when(invalidResult.getContext()).thenReturn(message);
|
||||
|
||||
gcmSender.sendMessage(message);
|
||||
|
||||
verify(sender, times(1)).send(any(Message.class), eq(message));
|
||||
verify(accountsManager, times(1)).get(eq(destinationNumber));
|
||||
verify(accountsManager, times(1)).update(eq(destinationAccount));
|
||||
verify(destinationDevice, times(1)).setGcmId(eq((String)null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanonicalId() {
|
||||
String destinationNumber = "+12223334444";
|
||||
String gcmId = "foo";
|
||||
String canonicalId = "bar";
|
||||
|
||||
AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
Sender sender = mock(Sender.class );
|
||||
Result canonicalResult = mock(Result.class );
|
||||
SynchronousExecutorService executorService = new SynchronousExecutorService();
|
||||
|
||||
Account destinationAccount = mock(Account.class );
|
||||
Device destinationDevice = mock(Device.class );
|
||||
DirectoryQueue directoryQueue = mock(DirectoryQueue.class);
|
||||
|
||||
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
|
||||
when(accountsManager.get(destinationNumber)).thenReturn(Optional.of(destinationAccount));
|
||||
when(destinationDevice.getGcmId()).thenReturn(gcmId);
|
||||
|
||||
when(canonicalResult.isInvalidRegistrationId()).thenReturn(false);
|
||||
when(canonicalResult.isUnregistered()).thenReturn(false);
|
||||
when(canonicalResult.hasCanonicalRegistrationId()).thenReturn(true);
|
||||
when(canonicalResult.isSuccess()).thenReturn(false);
|
||||
when(canonicalResult.getCanonicalRegistrationId()).thenReturn(canonicalId);
|
||||
|
||||
GcmMessage message = new GcmMessage(gcmId, destinationNumber, 1, false);
|
||||
GCMSender gcmSender = new GCMSender(accountsManager, sender, directoryQueue, executorService);
|
||||
|
||||
SettableFuture<Result> invalidFuture = SettableFuture.create();
|
||||
invalidFuture.set(canonicalResult);
|
||||
|
||||
when(sender.send(any(Message.class), Matchers.anyObject())).thenReturn(invalidFuture);
|
||||
when(canonicalResult.getContext()).thenReturn(message);
|
||||
|
||||
gcmSender.sendMessage(message);
|
||||
|
||||
verify(sender, times(1)).send(any(Message.class), eq(message));
|
||||
verify(accountsManager, times(1)).get(eq(destinationNumber));
|
||||
verify(accountsManager, times(1)).update(eq(destinationAccount));
|
||||
verify(destinationDevice, times(1)).setGcmId(eq(canonicalId));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package org.whispersystems.textsecuregcm.tests.redis;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.*;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.JedisPool;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
public class ReplicatedJedisPoolTest {
|
||||
|
||||
@Test
|
||||
public void testWriteCheckoutNoSlaves() {
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
|
||||
try {
|
||||
new ReplicatedJedisPool("testWriteCheckoutNoSlaves", master, new LinkedList<>(), new CircuitBreakerConfiguration());
|
||||
throw new AssertionError();
|
||||
} catch (Exception e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWriteCheckoutWithSlaves() {
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
JedisPool slave = mock(JedisPool.class);
|
||||
Jedis instance = mock(Jedis.class );
|
||||
|
||||
when(master.getResource()).thenReturn(instance);
|
||||
|
||||
ReplicatedJedisPool replicatedJedisPool = new ReplicatedJedisPool("testWriteCheckoutWithSlaves", master, Collections.singletonList(slave), new CircuitBreakerConfiguration());
|
||||
Jedis writeResource = replicatedJedisPool.getWriteResource();
|
||||
|
||||
assertThat(writeResource).isEqualTo(instance);
|
||||
verify(master, times(1)).getResource();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReadCheckouts() {
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
JedisPool slaveOne = mock(JedisPool.class);
|
||||
JedisPool slaveTwo = mock(JedisPool.class);
|
||||
Jedis instanceOne = mock(Jedis.class );
|
||||
Jedis instanceTwo = mock(Jedis.class );
|
||||
|
||||
when(slaveOne.getResource()).thenReturn(instanceOne);
|
||||
when(slaveTwo.getResource()).thenReturn(instanceTwo);
|
||||
|
||||
ReplicatedJedisPool replicatedJedisPool = new ReplicatedJedisPool("testReadCheckouts", master, Arrays.asList(slaveOne, slaveTwo), new CircuitBreakerConfiguration());
|
||||
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceOne);
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceTwo);
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceOne);
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceTwo);
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceOne);
|
||||
|
||||
verifyNoMoreInteractions(master);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBrokenReadCheckout() {
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
JedisPool slaveOne = mock(JedisPool.class);
|
||||
JedisPool slaveTwo = mock(JedisPool.class);
|
||||
Jedis instanceTwo = mock(Jedis.class );
|
||||
|
||||
when(slaveOne.getResource()).thenThrow(new JedisException("Connection failed!"));
|
||||
when(slaveTwo.getResource()).thenReturn(instanceTwo);
|
||||
|
||||
ReplicatedJedisPool replicatedJedisPool = new ReplicatedJedisPool("testBrokenReadCheckout", master, Arrays.asList(slaveOne, slaveTwo), new CircuitBreakerConfiguration());
|
||||
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceTwo);
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceTwo);
|
||||
assertThat(replicatedJedisPool.getReadResource()).isEqualTo(instanceTwo);
|
||||
|
||||
verifyNoMoreInteractions(master);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAllBrokenReadCheckout() {
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
JedisPool slaveOne = mock(JedisPool.class);
|
||||
JedisPool slaveTwo = mock(JedisPool.class);
|
||||
|
||||
when(slaveOne.getResource()).thenThrow(new JedisException("Connection failed!"));
|
||||
when(slaveTwo.getResource()).thenThrow(new JedisException("Also failed!"));
|
||||
|
||||
ReplicatedJedisPool replicatedJedisPool = new ReplicatedJedisPool("testAllBrokenReadCheckout", master, Arrays.asList(slaveOne, slaveTwo), new CircuitBreakerConfiguration());
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getReadResource();
|
||||
throw new AssertionError();
|
||||
} catch (Exception e) {
|
||||
// good
|
||||
}
|
||||
|
||||
verifyNoMoreInteractions(master);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCircuitBreakerOpen() {
|
||||
CircuitBreakerConfiguration configuration = new CircuitBreakerConfiguration();
|
||||
configuration.setFailureRateThreshold(50);
|
||||
configuration.setRingBufferSizeInClosedState(2);
|
||||
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
JedisPool slaveOne = mock(JedisPool.class);
|
||||
JedisPool slaveTwo = mock(JedisPool.class);
|
||||
|
||||
when(master.getResource()).thenReturn(null);
|
||||
when(slaveOne.getResource()).thenThrow(new JedisException("Connection failed!"));
|
||||
when(slaveTwo.getResource()).thenThrow(new JedisException("Also failed!"));
|
||||
|
||||
ReplicatedJedisPool replicatedJedisPool = new ReplicatedJedisPool("testCircuitBreakerOpen", master, Arrays.asList(slaveOne, slaveTwo), configuration);
|
||||
replicatedJedisPool.getWriteResource();
|
||||
|
||||
when(master.getResource()).thenThrow(new JedisException("Master broken!"));
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (JedisException exception) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (CircuitBreakerOpenException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCircuitBreakerHalfOpen() throws InterruptedException {
|
||||
CircuitBreakerConfiguration configuration = new CircuitBreakerConfiguration();
|
||||
configuration.setFailureRateThreshold(50);
|
||||
configuration.setRingBufferSizeInClosedState(2);
|
||||
configuration.setRingBufferSizeInHalfOpenState(1);
|
||||
configuration.setWaitDurationInOpenStateInSeconds(1);
|
||||
|
||||
JedisPool master = mock(JedisPool.class);
|
||||
JedisPool slaveOne = mock(JedisPool.class);
|
||||
JedisPool slaveTwo = mock(JedisPool.class);
|
||||
|
||||
when(master.getResource()).thenThrow(new JedisException("Master broken!"));
|
||||
when(slaveOne.getResource()).thenThrow(new JedisException("Connection failed!"));
|
||||
when(slaveTwo.getResource()).thenThrow(new JedisException("Also failed!"));
|
||||
|
||||
ReplicatedJedisPool replicatedJedisPool = new ReplicatedJedisPool("testCircuitBreakerHalfOpen", master, Arrays.asList(slaveOne, slaveTwo), configuration);
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (JedisException exception) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (JedisException exception) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (CircuitBreakerOpenException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
Thread.sleep(1100);
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (JedisException exception) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
replicatedJedisPool.getWriteResource();
|
||||
throw new AssertionError();
|
||||
} catch (CircuitBreakerOpenException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.whispersystems.textsecuregcm.tests.s3;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.s3.PolicySigner;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.time.Instant;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.ZonedDateTime;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class PolicySignerTest {
|
||||
|
||||
@Test
|
||||
public void testSignature() throws UnsupportedEncodingException {
|
||||
Instant time = Instant.parse("2015-12-29T00:00:00Z");
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(time, ZoneOffset.UTC);
|
||||
String encodedPolicy = "eyAiZXhwaXJhdGlvbiI6ICIyMDE1LTEyLTMwVDEyOjAwOjAwLjAwMFoiLA0KICAiY29uZGl0aW9ucyI6IFsNCiAgICB7ImJ1Y2tldCI6ICJzaWd2NGV4YW1wbGVidWNrZXQifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRrZXkiLCAidXNlci91c2VyMS8iXSwNCiAgICB7ImFjbCI6ICJwdWJsaWMtcmVhZCJ9LA0KICAgIHsic3VjY2Vzc19hY3Rpb25fcmVkaXJlY3QiOiAiaHR0cDovL3NpZ3Y0ZXhhbXBsZWJ1Y2tldC5zMy5hbWF6b25hd3MuY29tL3N1Y2Nlc3NmdWxfdXBsb2FkLmh0bWwifSwNCiAgICBbInN0YXJ0cy13aXRoIiwgIiRDb250ZW50LVR5cGUiLCAiaW1hZ2UvIl0sDQogICAgeyJ4LWFtei1tZXRhLXV1aWQiOiAiMTQzNjUxMjM2NTEyNzQifSwNCiAgICB7IngtYW16LXNlcnZlci1zaWRlLWVuY3J5cHRpb24iOiAiQUVTMjU2In0sDQogICAgWyJzdGFydHMtd2l0aCIsICIkeC1hbXotbWV0YS10YWciLCAiIl0sDQoNCiAgICB7IngtYW16LWNyZWRlbnRpYWwiOiAiQUtJQUlPU0ZPRE5ON0VYQU1QTEUvMjAxNTEyMjkvdXMtZWFzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LA0KICAgIHsieC1hbXotYWxnb3JpdGhtIjogIkFXUzQtSE1BQy1TSEEyNTYifSwNCiAgICB7IngtYW16LWRhdGUiOiAiMjAxNTEyMjlUMDAwMDAwWiIgfQ0KICBdDQp9";
|
||||
PolicySigner policySigner = new PolicySigner("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", "us-east-1");
|
||||
|
||||
assertEquals(policySigner.getSignature(zonedDateTime, encodedPolicy), "8afdbf4008c03f22c2cd3cdb72e4afbb1f6a588f3255ac628749a66d7f09699e");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package org.whispersystems.sms;
|
||||
|
||||
import com.github.tomakehurst.wiremock.junit.WireMockRule;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration;
|
||||
import org.whispersystems.textsecuregcm.sms.TwilioSmsSender;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.github.tomakehurst.wiremock.client.WireMock.*;
|
||||
import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class TwilioSmsSenderTest {
|
||||
|
||||
private static final String ACCOUNT_ID = "test_account_id";
|
||||
private static final String ACCOUNT_TOKEN = "test_account_token";
|
||||
private static final List<String> NUMBERS = List.of("+14151111111", "+14152222222");
|
||||
private static final String MESSAGING_SERVICES_ID = "test_messaging_services_id";
|
||||
private static final String LOCAL_DOMAIN = "test.com";
|
||||
|
||||
@Rule
|
||||
public WireMockRule wireMockRule = new WireMockRule(options().dynamicPort().dynamicHttpsPort());
|
||||
|
||||
@Test
|
||||
public void testSendSms() {
|
||||
wireMockRule.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
|
||||
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody("{\"price\": -0.00750, \"status\": \"sent\"}")));
|
||||
|
||||
|
||||
TwilioConfiguration configuration = new TwilioConfiguration();
|
||||
configuration.setAccountId(ACCOUNT_ID);
|
||||
configuration.setAccountToken(ACCOUNT_TOKEN);
|
||||
configuration.setNumbers(NUMBERS);
|
||||
configuration.setMessagingServicesId(MESSAGING_SERVICES_ID);
|
||||
configuration.setLocalDomain(LOCAL_DOMAIN);
|
||||
|
||||
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMockRule.port(), configuration);
|
||||
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
|
||||
|
||||
assertThat(success).isTrue();
|
||||
|
||||
verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
|
||||
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
|
||||
.withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B14153333333&Body=%3C%23%3E+Your+Signal+verification+code%3A+123-456%0A%0AdoDiFGKPO1r")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendVox() {
|
||||
wireMockRule.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
|
||||
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
|
||||
.willReturn(aResponse()
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody("{\"price\": -0.00750, \"status\": \"completed\"}")));
|
||||
|
||||
|
||||
TwilioConfiguration configuration = new TwilioConfiguration();
|
||||
configuration.setAccountId(ACCOUNT_ID);
|
||||
configuration.setAccountToken(ACCOUNT_TOKEN);
|
||||
configuration.setNumbers(NUMBERS);
|
||||
configuration.setMessagingServicesId(MESSAGING_SERVICES_ID);
|
||||
configuration.setLocalDomain(LOCAL_DOMAIN);
|
||||
|
||||
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMockRule.port(), configuration);
|
||||
boolean success = sender.deliverVoxVerification("+14153333333", "123-456", Optional.of("en_US")).join();
|
||||
|
||||
assertThat(success).isTrue();
|
||||
|
||||
verify(1, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
|
||||
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
|
||||
.withRequestBody(matching("To=%2B14153333333&From=%2B1415(1111111|2222222)&Url=https%3A%2F%2Ftest.com%2Fv1%2Fvoice%2Fdescription%2F123-456%3Fl%3Den_US")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendSmsFiveHundered() {
|
||||
wireMockRule.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
|
||||
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
|
||||
.willReturn(aResponse()
|
||||
.withStatus(500)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody("{\"message\": \"Server error!\"}")));
|
||||
|
||||
|
||||
TwilioConfiguration configuration = new TwilioConfiguration();
|
||||
configuration.setAccountId(ACCOUNT_ID);
|
||||
configuration.setAccountToken(ACCOUNT_TOKEN);
|
||||
configuration.setNumbers(NUMBERS);
|
||||
configuration.setMessagingServicesId(MESSAGING_SERVICES_ID);
|
||||
configuration.setLocalDomain(LOCAL_DOMAIN);
|
||||
|
||||
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMockRule.port(), configuration);
|
||||
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
|
||||
|
||||
assertThat(success).isFalse();
|
||||
|
||||
verify(3, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Messages.json"))
|
||||
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
|
||||
.withRequestBody(equalTo("MessagingServiceSid=test_messaging_services_id&To=%2B14153333333&Body=%3C%23%3E+Your+Signal+verification+code%3A+123-456%0A%0AdoDiFGKPO1r")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendVoxFiveHundred() {
|
||||
wireMockRule.stubFor(post(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
|
||||
.withBasicAuth(ACCOUNT_ID, ACCOUNT_TOKEN)
|
||||
.willReturn(aResponse()
|
||||
.withStatus(500)
|
||||
.withHeader("Content-Type", "application/json")
|
||||
.withBody("{\"message\": \"Server error!\"}")));
|
||||
|
||||
TwilioConfiguration configuration = new TwilioConfiguration();
|
||||
configuration.setAccountId(ACCOUNT_ID);
|
||||
configuration.setAccountToken(ACCOUNT_TOKEN);
|
||||
configuration.setNumbers(NUMBERS);
|
||||
configuration.setMessagingServicesId(MESSAGING_SERVICES_ID);
|
||||
configuration.setLocalDomain(LOCAL_DOMAIN);
|
||||
|
||||
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + wireMockRule.port(), configuration);
|
||||
boolean success = sender.deliverVoxVerification("+14153333333", "123-456", Optional.of("en_US")).join();
|
||||
|
||||
assertThat(success).isFalse();
|
||||
|
||||
verify(3, postRequestedFor(urlEqualTo("/2010-04-01/Accounts/" + ACCOUNT_ID + "/Calls.json"))
|
||||
.withHeader("Content-Type", equalTo("application/x-www-form-urlencoded"))
|
||||
.withRequestBody(matching("To=%2B14153333333&From=%2B1415(1111111|2222222)&Url=https%3A%2F%2Ftest.com%2Fv1%2Fvoice%2Fdescription%2F123-456%3Fl%3Den_US")));
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendSmsNetworkFailure() {
|
||||
TwilioConfiguration configuration = new TwilioConfiguration();
|
||||
configuration.setAccountId(ACCOUNT_ID);
|
||||
configuration.setAccountToken(ACCOUNT_TOKEN);
|
||||
configuration.setNumbers(NUMBERS);
|
||||
configuration.setMessagingServicesId(MESSAGING_SERVICES_ID);
|
||||
configuration.setLocalDomain(LOCAL_DOMAIN);
|
||||
|
||||
TwilioSmsSender sender = new TwilioSmsSender("http://localhost:" + 39873, configuration);
|
||||
boolean success = sender.deliverSmsVerification("+14153333333", Optional.of("android-ng"), "123-456").join();
|
||||
|
||||
assertThat(success).isFalse();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRule;
|
||||
import org.whispersystems.textsecuregcm.storage.AbusiveHostRules;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class AbusiveHostRulesTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("abusedb.xml"));
|
||||
|
||||
private AbusiveHostRules abusiveHostRules;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.abusiveHostRules = new AbusiveHostRules(new FaultTolerantDatabase("abusive_hosts-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockedHost() throws SQLException {
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("INSERT INTO abusive_host_rules (host, blocked) VALUES (?::INET, ?)");
|
||||
statement.setString(1, "192.168.1.1");
|
||||
statement.setInt(2, 1);
|
||||
statement.execute();
|
||||
|
||||
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("192.168.1.1");
|
||||
assertThat(rules.size()).isEqualTo(1);
|
||||
assertThat(rules.get(0).getRegions().isEmpty()).isTrue();
|
||||
assertThat(rules.get(0).getHost()).isEqualTo("192.168.1.1");
|
||||
assertThat(rules.get(0).isBlocked()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBlockedCidr() throws SQLException {
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("INSERT INTO abusive_host_rules (host, blocked) VALUES (?::INET, ?)");
|
||||
statement.setString(1, "192.168.1.0/24");
|
||||
statement.setInt(2, 1);
|
||||
statement.execute();
|
||||
|
||||
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("192.168.1.1");
|
||||
assertThat(rules.size()).isEqualTo(1);
|
||||
assertThat(rules.get(0).getRegions().isEmpty()).isTrue();
|
||||
assertThat(rules.get(0).getHost()).isEqualTo("192.168.1.0/24");
|
||||
assertThat(rules.get(0).isBlocked()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnblocked() throws SQLException {
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("INSERT INTO abusive_host_rules (host, blocked) VALUES (?::INET, ?)");
|
||||
statement.setString(1, "192.168.1.0/24");
|
||||
statement.setInt(2, 1);
|
||||
statement.execute();
|
||||
|
||||
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("172.17.1.1");
|
||||
assertThat(rules.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestricted() throws SQLException {
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("INSERT INTO abusive_host_rules (host, blocked, regions) VALUES (?::INET, ?, ?)");
|
||||
statement.setString(1, "192.168.1.0/24");
|
||||
statement.setInt(2, 0);
|
||||
statement.setString(3, "+1,+49");
|
||||
statement.execute();
|
||||
|
||||
List<AbusiveHostRule> rules = abusiveHostRules.getAbusiveHostRulesFor("192.168.1.100");
|
||||
assertThat(rules.size()).isEqualTo(1);
|
||||
assertThat(rules.get(0).isBlocked()).isFalse();
|
||||
assertThat(rules.get(0).getRegions()).isEqualTo(Arrays.asList("+1", "+49"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertBlocked() throws Exception {
|
||||
abusiveHostRules.setBlockedHost("172.17.0.1", "Testing one two");
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * from abusive_host_rules WHERE host = ?::inet");
|
||||
statement.setString(1, "172.17.0.1");
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
assertThat(resultSet.next()).isTrue();
|
||||
|
||||
assertThat(resultSet.getInt("blocked")).isEqualTo(1);
|
||||
assertThat(resultSet.getString("regions")).isNullOrEmpty();
|
||||
assertThat(resultSet.getString("notes")).isEqualTo("Testing one two");
|
||||
|
||||
abusiveHostRules.setBlockedHost("172.17.0.1", "Different notes");
|
||||
|
||||
|
||||
statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * from abusive_host_rules WHERE host = ?::inet");
|
||||
statement.setString(1, "172.17.0.1");
|
||||
|
||||
resultSet = statement.executeQuery();
|
||||
|
||||
assertThat(resultSet.next()).isTrue();
|
||||
|
||||
assertThat(resultSet.getInt("blocked")).isEqualTo(1);
|
||||
assertThat(resultSet.getString("regions")).isNullOrEmpty();
|
||||
assertThat(resultSet.getString("notes")).isEqualTo("Testing one two");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Copyright (C) 2018 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawler;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerCache;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerListener;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class AccountDatabaseCrawlerTest {
|
||||
|
||||
private static final String ACCOUNT1 = "+1";
|
||||
private static final String ACCOUNT2 = "+2";
|
||||
|
||||
private static final int CHUNK_SIZE = 1000;
|
||||
private static final long CHUNK_INTERVAL_MS = 30_000L;
|
||||
|
||||
private final Account account1 = mock(Account.class);
|
||||
private final Account account2 = mock(Account.class);
|
||||
|
||||
private final Accounts accounts = mock(Accounts.class);
|
||||
private final AccountDatabaseCrawlerListener listener = mock(AccountDatabaseCrawlerListener.class);
|
||||
private final AccountDatabaseCrawlerCache cache = mock(AccountDatabaseCrawlerCache.class);
|
||||
|
||||
private final AccountDatabaseCrawler crawler = new AccountDatabaseCrawler(accounts, cache, Arrays.asList(listener), CHUNK_SIZE, CHUNK_INTERVAL_MS);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(account1.getNumber()).thenReturn(ACCOUNT1);
|
||||
when(account2.getNumber()).thenReturn(ACCOUNT2);
|
||||
|
||||
when(accounts.getAllFrom(anyInt())).thenReturn(Arrays.asList(account1, account2));
|
||||
when(accounts.getAllFrom(eq(ACCOUNT1), anyInt())).thenReturn(Arrays.asList(account2));
|
||||
when(accounts.getAllFrom(eq(ACCOUNT2), anyInt())).thenReturn(Collections.emptyList());
|
||||
|
||||
when(cache.claimActiveWork(any(), anyLong())).thenReturn(true);
|
||||
when(cache.isAccelerated()).thenReturn(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlStart() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.getLastNumber()).thenReturn(Optional.empty());
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(1)).getLastNumber();
|
||||
verify(listener, times(1)).onCrawlStart();
|
||||
verify(accounts, times(1)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(0)).getAllFrom(any(String.class), eq(CHUNK_SIZE));
|
||||
verify(account1, times(0)).getNumber();
|
||||
verify(account2, times(1)).getNumber();
|
||||
verify(listener, times(1)).onCrawlChunk(eq(Optional.empty()), eq(Arrays.asList(account1, account2)));
|
||||
verify(cache, times(1)).setLastNumber(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyNoMoreInteractions(account1);
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunk() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT1));
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(1)).getLastNumber();
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
verify(account2, times(1)).getNumber();
|
||||
verify(listener, times(1)).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(1)).setLastNumber(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunkAccelerated() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.isAccelerated()).thenReturn(true);
|
||||
when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT1));
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isTrue();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(1)).getLastNumber();
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
verify(account2, times(1)).getNumber();
|
||||
verify(listener, times(1)).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(1)).setLastNumber(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunkRestart() throws AccountDatabaseCrawlerRestartException {
|
||||
when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT1));
|
||||
doThrow(AccountDatabaseCrawlerRestartException.class).when(listener).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(1)).getLastNumber();
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT1), eq(CHUNK_SIZE));
|
||||
verify(account2, times(0)).getNumber();
|
||||
verify(listener, times(1)).onCrawlChunk(eq(Optional.of(ACCOUNT1)), eq(Arrays.asList(account2)));
|
||||
verify(cache, times(1)).setLastNumber(eq(Optional.empty()));
|
||||
verify(cache, times(1)).clearAccelerate();
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
|
||||
verifyNoMoreInteractions(account2);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlEnd() {
|
||||
when(cache.getLastNumber()).thenReturn(Optional.of(ACCOUNT2));
|
||||
|
||||
boolean accelerated = crawler.doPeriodicWork();
|
||||
assertThat(accelerated).isFalse();
|
||||
|
||||
verify(cache, times(1)).claimActiveWork(any(String.class), anyLong());
|
||||
verify(cache, times(1)).getLastNumber();
|
||||
verify(accounts, times(0)).getAllFrom(eq(CHUNK_SIZE));
|
||||
verify(accounts, times(1)).getAllFrom(eq(ACCOUNT2), eq(CHUNK_SIZE));
|
||||
verify(account1, times(0)).getNumber();
|
||||
verify(account2, times(0)).getNumber();
|
||||
verify(listener, times(1)).onCrawlEnd(eq(Optional.of(ACCOUNT2)));
|
||||
verify(cache, times(1)).setLastNumber(eq(Optional.empty()));
|
||||
verify(cache, times(1)).clearAccelerate();
|
||||
verify(cache, times(1)).isAccelerated();
|
||||
verify(cache, times(1)).releaseActiveWork(any(String.class));
|
||||
|
||||
verifyZeroInteractions(account1);
|
||||
verifyZeroInteractions(account2);
|
||||
|
||||
verifyNoMoreInteractions(accounts);
|
||||
verifyNoMoreInteractions(listener);
|
||||
verifyNoMoreInteractions(cache);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class AccountTest {
|
||||
|
||||
private final Device oldMasterDevice = mock(Device.class);
|
||||
private final Device recentMasterDevice = mock(Device.class);
|
||||
private final Device agingSecondaryDevice = mock(Device.class);
|
||||
private final Device recentSecondaryDevice = mock(Device.class);
|
||||
private final Device oldSecondaryDevice = mock(Device.class);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(oldMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366));
|
||||
when(oldMasterDevice.isActive()).thenReturn(true);
|
||||
when(oldMasterDevice.getId()).thenReturn(Device.MASTER_ID);
|
||||
|
||||
when(recentMasterDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(recentMasterDevice.isActive()).thenReturn(true);
|
||||
when(recentMasterDevice.getId()).thenReturn(Device.MASTER_ID);
|
||||
|
||||
when(agingSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(31));
|
||||
when(agingSecondaryDevice.isActive()).thenReturn(false);
|
||||
when(agingSecondaryDevice.getId()).thenReturn(2L);
|
||||
|
||||
when(recentSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1));
|
||||
when(recentSecondaryDevice.isActive()).thenReturn(true);
|
||||
when(recentSecondaryDevice.getId()).thenReturn(2L);
|
||||
|
||||
when(oldSecondaryDevice.getLastSeen()).thenReturn(System.currentTimeMillis() - TimeUnit.DAYS.toMillis(366));
|
||||
when(oldSecondaryDevice.isActive()).thenReturn(false);
|
||||
when(oldSecondaryDevice.getId()).thenReturn(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountActive() {
|
||||
Account recentAccount = new Account("+14152222222", new HashSet<Device>() {{
|
||||
add(recentMasterDevice);
|
||||
add(recentSecondaryDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
assertTrue(recentAccount.isActive());
|
||||
|
||||
Account oldSecondaryAccount = new Account("+14152222222", new HashSet<Device>() {{
|
||||
add(recentMasterDevice);
|
||||
add(agingSecondaryDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
assertTrue(oldSecondaryAccount.isActive());
|
||||
|
||||
Account agingPrimaryAccount = new Account("+14152222222", new HashSet<Device>() {{
|
||||
add(oldMasterDevice);
|
||||
add(agingSecondaryDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
assertTrue(agingPrimaryAccount.isActive());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAccountInactive() {
|
||||
Account oldPrimaryAccount = new Account("+14152222222", new HashSet<Device>() {{
|
||||
add(oldMasterDevice);
|
||||
add(oldSecondaryDevice);
|
||||
}}, "1234".getBytes());
|
||||
|
||||
assertFalse(oldPrimaryAccount.isActive());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Optional;
|
||||
|
||||
import static junit.framework.TestCase.assertSame;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import redis.clients.jedis.Jedis;
|
||||
import redis.clients.jedis.exceptions.JedisException;
|
||||
|
||||
public class AccountsManagerTest {
|
||||
|
||||
@Test
|
||||
public void testGetAccountInCache() {
|
||||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Accounts accounts = mock(Accounts.class );
|
||||
DirectoryManager directoryManager = mock(DirectoryManager.class );
|
||||
|
||||
when(cacheClient.getReadResource()).thenReturn(jedis);
|
||||
when(jedis.get(eq("Account5+14152222222"))).thenReturn("{\"number\": \"+14152222222\", \"name\": \"test\"}");
|
||||
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient);
|
||||
Optional<Account> account = accountsManager.get("+14152222222");
|
||||
|
||||
assertTrue(account.isPresent());
|
||||
assertEquals(account.get().getNumber(), "+14152222222");
|
||||
assertEquals(account.get().getProfileName(), "test");
|
||||
|
||||
verify(jedis, times(1)).get(eq("Account5+14152222222"));
|
||||
verify(jedis, times(1)).close();
|
||||
verifyNoMoreInteractions(jedis);
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountNotInCache() {
|
||||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Accounts accounts = mock(Accounts.class );
|
||||
DirectoryManager directoryManager = mock(DirectoryManager.class );
|
||||
Account account = new Account("+14152222222", new HashSet<>(), new byte[16]);
|
||||
|
||||
when(cacheClient.getReadResource()).thenReturn(jedis);
|
||||
when(cacheClient.getWriteResource()).thenReturn(jedis);
|
||||
when(jedis.get(eq("Account5+14152222222"))).thenReturn(null);
|
||||
when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account));
|
||||
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient);
|
||||
Optional<Account> retrieved = accountsManager.get("+14152222222");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(jedis, times(1)).get(eq("Account5+14152222222"));
|
||||
verify(jedis, times(1)).set(eq("Account5+14152222222"), anyString());
|
||||
verify(jedis, times(2)).close();
|
||||
verifyNoMoreInteractions(jedis);
|
||||
|
||||
verify(accounts, times(1)).get(eq("+14152222222"));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAccountBrokenCache() {
|
||||
ReplicatedJedisPool cacheClient = mock(ReplicatedJedisPool.class);
|
||||
Jedis jedis = mock(Jedis.class );
|
||||
Accounts accounts = mock(Accounts.class );
|
||||
DirectoryManager directoryManager = mock(DirectoryManager.class );
|
||||
Account account = new Account("+14152222222", new HashSet<>(), new byte[16]);
|
||||
|
||||
when(cacheClient.getReadResource()).thenReturn(jedis);
|
||||
when(cacheClient.getWriteResource()).thenReturn(jedis);
|
||||
when(jedis.get(eq("Account5+14152222222"))).thenThrow(new JedisException("Connection lost!"));
|
||||
when(accounts.get(eq("+14152222222"))).thenReturn(Optional.of(account));
|
||||
|
||||
AccountsManager accountsManager = new AccountsManager(accounts, directoryManager, cacheClient);
|
||||
Optional<Account> retrieved = accountsManager.get("+14152222222");
|
||||
|
||||
assertTrue(retrieved.isPresent());
|
||||
assertSame(retrieved.get(), account);
|
||||
|
||||
verify(jedis, times(1)).get(eq("Account5+14152222222"));
|
||||
verify(jedis, times(1)).set(eq("Account5+14152222222"), anyString());
|
||||
verify(jedis, times(2)).close();
|
||||
verifyNoMoreInteractions(jedis);
|
||||
|
||||
verify(accounts, times(1)).get(eq("+14152222222"));
|
||||
verifyNoMoreInteractions(accounts);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,303 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.HandleCallback;
|
||||
import org.jdbi.v3.core.HandleConsumer;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.jdbi.v3.core.transaction.TransactionException;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Accounts;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.mappers.AccountRowMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
public class AccountsTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private Accounts accounts;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
FaultTolerantDatabase faultTolerantDatabase = new FaultTolerantDatabase("accountsTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
new CircuitBreakerConfiguration());
|
||||
|
||||
this.accounts = new Accounts(faultTolerantDatabase);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStore() throws SQLException, IOException {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStoreMulti() throws SQLException, IOException {
|
||||
Set<Device> devices = new HashSet<>();
|
||||
devices.add(generateDevice(1));
|
||||
devices.add(generateDevice(2));
|
||||
|
||||
Account account = generateAccount("+14151112222", devices);
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieve() {
|
||||
Set<Device> devicesFirst = new HashSet<>();
|
||||
devicesFirst.add(generateDevice(1));
|
||||
devicesFirst.add(generateDevice(2));
|
||||
|
||||
Account accountFirst = generateAccount("+14151112222", devicesFirst);
|
||||
|
||||
Set<Device> devicesSecond = new HashSet<>();
|
||||
devicesSecond.add(generateDevice(1));
|
||||
devicesSecond.add(generateDevice(2));
|
||||
|
||||
Account accountSecond = generateAccount("+14152221111", devicesSecond);
|
||||
|
||||
accounts.create(accountFirst);
|
||||
accounts.create(accountSecond);
|
||||
|
||||
Optional<Account> retrievedFirst = accounts.get("+14151112222");
|
||||
Optional<Account> retrievedSecond = accounts.get("+14152221111");
|
||||
|
||||
assertThat(retrievedFirst.isPresent()).isTrue();
|
||||
assertThat(retrievedSecond.isPresent()).isTrue();
|
||||
|
||||
verifyStoredState("+14151112222", retrievedFirst.get(), accountFirst);
|
||||
verifyStoredState("+14152221111", retrievedSecond.get(), accountSecond);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverwrite() throws Exception {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM accounts WHERE number = ?");
|
||||
verifyStoredState(statement, "+14151112222", account);
|
||||
|
||||
device = generateDevice(1);
|
||||
account = generateAccount("+14151112222", Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
verifyStoredState(statement, "+14151112222", account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdate() {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
device.setName("foobar");
|
||||
|
||||
accounts.update(account);
|
||||
|
||||
Optional<Account> retrieved = accounts.get("+14151112222");
|
||||
|
||||
assertThat(retrieved.isPresent()).isTrue();
|
||||
verifyStoredState("+14151112222", retrieved.get(), account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieveFrom() {
|
||||
List<Account> users = new ArrayList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
Account account = generateAccount("+1" + String.format("%03d", i));
|
||||
users.add(account);
|
||||
accounts.create(account);
|
||||
}
|
||||
|
||||
List<Account> retrieved = accounts.getAllFrom(10);
|
||||
assertThat(retrieved.size()).isEqualTo(10);
|
||||
|
||||
for (int i=0;i<retrieved.size();i++) {
|
||||
verifyStoredState("+1" + String.format("%03d", (i + 1)), retrieved.get(i), users.get(i));
|
||||
}
|
||||
|
||||
for (int j=0;j<9;j++) {
|
||||
retrieved = accounts.getAllFrom(retrieved.get(9).getNumber(), 10);
|
||||
assertThat(retrieved.size()).isEqualTo(10);
|
||||
|
||||
for (int i=0;i<retrieved.size();i++) {
|
||||
verifyStoredState("+1" + String.format("%03d", (10 + (j * 10) + i + 1)), retrieved.get(i), users.get(10 + (j * 10) + i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVacuum() {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
accounts.vacuum();
|
||||
|
||||
Optional<Account> retrieved = accounts.get("+14151112222");
|
||||
assertThat(retrieved.isPresent()).isTrue();
|
||||
|
||||
verifyStoredState("+14151112222", retrieved.get(), account);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMissing() {
|
||||
Device device = generateDevice (1 );
|
||||
Account account = generateAccount("+14151112222", Collections.singleton(device));
|
||||
|
||||
accounts.create(account);
|
||||
|
||||
Optional<Account> retrieved = accounts.get("+11111111");
|
||||
assertThat(retrieved.isPresent()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreaker() throws InterruptedException {
|
||||
Jdbi jdbi = mock(Jdbi.class);
|
||||
doThrow(new TransactionException("Database error!")).when(jdbi).useHandle(any(HandleConsumer.class));
|
||||
|
||||
CircuitBreakerConfiguration configuration = new CircuitBreakerConfiguration();
|
||||
configuration.setWaitDurationInOpenStateInSeconds(1);
|
||||
configuration.setRingBufferSizeInHalfOpenState(1);
|
||||
configuration.setRingBufferSizeInClosedState(2);
|
||||
configuration.setFailureRateThreshold(50);
|
||||
|
||||
Accounts accounts = new Accounts(new FaultTolerantDatabase("testAccountBreaker", jdbi, configuration));
|
||||
Account account = generateAccount("+14151112222");
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (CircuitBreakerOpenException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
Thread.sleep(1100);
|
||||
|
||||
try {
|
||||
accounts.update(account);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private Device generateDevice(long id) {
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
SignedPreKey signedPreKey = new SignedPreKey(random.nextInt(), "testPublicKey-" + random.nextInt(), "testSignature-" + random.nextInt());
|
||||
return new Device(id, "testName-" + random.nextInt(), "testAuthToken-" + random.nextInt(), "testSalt-" + random.nextInt(), null, "testGcmId-" + random.nextInt(), "testApnId-" + random.nextInt(), "testVoipApnId-" + random.nextInt(), random.nextBoolean(), random.nextInt(), signedPreKey, random.nextInt(), random.nextInt(), "testUserAgent-" + random.nextInt(), random.nextBoolean());
|
||||
}
|
||||
|
||||
private Account generateAccount(String number) {
|
||||
Device device = generateDevice(1);
|
||||
return generateAccount(number, Collections.singleton(device));
|
||||
}
|
||||
|
||||
private Account generateAccount(String number, Set<Device> devices) {
|
||||
byte[] unidentifiedAccessKey = new byte[16];
|
||||
Random random = new Random(System.currentTimeMillis());
|
||||
Arrays.fill(unidentifiedAccessKey, (byte)random.nextInt(255));
|
||||
|
||||
return new Account(number, devices, unidentifiedAccessKey);
|
||||
}
|
||||
|
||||
private void verifyStoredState(PreparedStatement statement, String number, Account expecting)
|
||||
throws SQLException, IOException
|
||||
{
|
||||
statement.setString(1, number);
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
if (resultSet.next()) {
|
||||
String data = resultSet.getString("data");
|
||||
assertThat(data).isNotEmpty();
|
||||
|
||||
Account result = new AccountRowMapper().map(resultSet, null);
|
||||
verifyStoredState(number, result, expecting);
|
||||
} else {
|
||||
throw new AssertionError("No data");
|
||||
}
|
||||
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
private void verifyStoredState(String number, Account result, Account expecting) {
|
||||
assertThat(result.getNumber()).isEqualTo(number);
|
||||
assertThat(result.getLastSeen()).isEqualTo(expecting.getLastSeen());
|
||||
assertThat(Arrays.equals(result.getUnidentifiedAccessKey().get(), expecting.getUnidentifiedAccessKey().get())).isTrue();
|
||||
|
||||
for (Device expectingDevice : expecting.getDevices()) {
|
||||
Device resultDevice = result.getDevice(expectingDevice.getId()).get();
|
||||
assertThat(resultDevice.getApnId()).isEqualTo(expectingDevice.getApnId());
|
||||
assertThat(resultDevice.getGcmId()).isEqualTo(expectingDevice.getGcmId());
|
||||
assertThat(resultDevice.getLastSeen()).isEqualTo(expectingDevice.getLastSeen());
|
||||
assertThat(resultDevice.getSignedPreKey().getPublicKey()).isEqualTo(expectingDevice.getSignedPreKey().getPublicKey());
|
||||
assertThat(resultDevice.getSignedPreKey().getKeyId()).isEqualTo(expectingDevice.getSignedPreKey().getKeyId());
|
||||
assertThat(resultDevice.getSignedPreKey().getSignature()).isEqualTo(expectingDevice.getSignedPreKey().getSignature());
|
||||
assertThat(resultDevice.getFetchesMessages()).isEqualTo(expectingDevice.getFetchesMessages());
|
||||
assertThat(resultDevice.getUserAgent()).isEqualTo(expectingDevice.getUserAgent());
|
||||
assertThat(resultDevice.getName()).isEqualTo(expectingDevice.getName());
|
||||
assertThat(resultDevice.getCreated()).isEqualTo(expectingDevice.getCreated());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
/**
|
||||
* Copyright (C) 2018 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import org.whispersystems.textsecuregcm.redis.ReplicatedJedisPool;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.ActiveUserCounter;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import io.dropwizard.metrics.MetricsFactory;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import redis.clients.jedis.Jedis;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ActiveUserCounterTest {
|
||||
|
||||
private final String NUMBER_IOS = "+15551234567";
|
||||
private final String NUMBER_ANDROID = "+5511987654321";
|
||||
private final String NUMBER_NODEVICE = "+5215551234567";
|
||||
|
||||
private final String TALLY_KEY = "active_user_tally";
|
||||
|
||||
private final Device iosDevice = mock(Device.class);
|
||||
private final Device androidDevice = mock(Device.class);
|
||||
|
||||
private final Account androidAccount = mock(Account.class);
|
||||
private final Account iosAccount = mock(Account.class);
|
||||
private final Account noDeviceAccount = mock(Account.class);
|
||||
|
||||
private final Jedis jedis = mock(Jedis.class);
|
||||
private final ReplicatedJedisPool jedisPool = mock(ReplicatedJedisPool.class);
|
||||
private final MetricsFactory metricsFactory = mock(MetricsFactory.class);
|
||||
|
||||
private final ActiveUserCounter activeUserCounter = new ActiveUserCounter(metricsFactory, jedisPool);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
long halfDayAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(12);
|
||||
long fortyFiveDayAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(45);
|
||||
|
||||
when(androidDevice.getApnId()).thenReturn(null);
|
||||
when(androidDevice.getGcmId()).thenReturn("mock-gcm-id");
|
||||
when(androidDevice.getLastSeen()).thenReturn(fortyFiveDayAgo);
|
||||
|
||||
when(iosDevice.getApnId()).thenReturn("mock-apn-id");
|
||||
when(iosDevice.getGcmId()).thenReturn(null);
|
||||
when(iosDevice.getLastSeen()).thenReturn(halfDayAgo);
|
||||
|
||||
when(iosAccount.getNumber()).thenReturn(NUMBER_IOS);
|
||||
when(iosAccount.getMasterDevice()).thenReturn(Optional.of(iosDevice));
|
||||
|
||||
when(androidAccount.getNumber()).thenReturn(NUMBER_ANDROID);
|
||||
when(androidAccount.getMasterDevice()).thenReturn(Optional.of(androidDevice));
|
||||
|
||||
when(noDeviceAccount.getNumber()).thenReturn(NUMBER_NODEVICE);
|
||||
when(noDeviceAccount.getMasterDevice()).thenReturn(Optional.ofNullable(null));
|
||||
|
||||
when(jedis.get(any(String.class))).thenReturn("{\"fromNumber\":\"+\",\"platforms\":{},\"countries\":{}}");
|
||||
when(jedisPool.getWriteResource()).thenReturn(jedis);
|
||||
when(jedisPool.getReadResource()).thenReturn(jedis);
|
||||
when(metricsFactory.getReporters()).thenReturn(ImmutableList.of());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlStart() {
|
||||
activeUserCounter.onCrawlStart();
|
||||
|
||||
verify(jedisPool, times(1)).getWriteResource();
|
||||
verify(jedis, times(1)).del(any(String.class));
|
||||
verify(jedis, times(1)).close();
|
||||
|
||||
verifyZeroInteractions(iosDevice);
|
||||
verifyZeroInteractions(iosAccount);
|
||||
verifyZeroInteractions(androidDevice);
|
||||
verifyZeroInteractions(androidAccount);
|
||||
verifyZeroInteractions(noDeviceAccount);
|
||||
verifyZeroInteractions(metricsFactory);
|
||||
verifyNoMoreInteractions(jedis);
|
||||
verifyNoMoreInteractions(jedisPool);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlEnd() {
|
||||
activeUserCounter.onCrawlEnd(Optional.empty());
|
||||
|
||||
verify(jedisPool, times(1)).getReadResource();
|
||||
verify(jedis, times(1)).get(any(String.class));
|
||||
verify(jedis, times(1)).close();
|
||||
|
||||
verify(metricsFactory, times(1)).getReporters();
|
||||
|
||||
verifyZeroInteractions(iosDevice);
|
||||
verifyZeroInteractions(iosAccount);
|
||||
verifyZeroInteractions(androidDevice);
|
||||
verifyZeroInteractions(androidAccount);
|
||||
verifyZeroInteractions(noDeviceAccount);
|
||||
|
||||
verifyNoMoreInteractions(metricsFactory);
|
||||
verifyNoMoreInteractions(jedis);
|
||||
verifyNoMoreInteractions(jedisPool);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunkValidAccount() throws AccountDatabaseCrawlerRestartException {
|
||||
activeUserCounter.onCrawlChunk(Optional.of(NUMBER_IOS), Arrays.asList(iosAccount));
|
||||
|
||||
verify(iosAccount, times(1)).getMasterDevice();
|
||||
verify(iosAccount, times(1)).getNumber();
|
||||
|
||||
verify(iosDevice, times(1)).getLastSeen();
|
||||
verify(iosDevice, times(1)).getApnId();
|
||||
verify(iosDevice, times(0)).getGcmId();
|
||||
|
||||
verify(jedisPool, times(1)).getWriteResource();
|
||||
verify(jedis, times(1)).get(any(String.class));
|
||||
verify(jedis, times(1)).set(any(String.class), eq("{\"fromNumber\":\""+NUMBER_IOS+"\",\"platforms\":{\"ios\":[1,1,1,1,1]},\"countries\":{\"1\":[1,1,1,1,1]}}"));
|
||||
verify(jedis, times(1)).close();
|
||||
|
||||
verify(metricsFactory, times(0)).getReporters();
|
||||
|
||||
verifyZeroInteractions(androidDevice);
|
||||
verifyZeroInteractions(androidAccount);
|
||||
verifyZeroInteractions(noDeviceAccount);
|
||||
verifyZeroInteractions(metricsFactory);
|
||||
|
||||
verifyNoMoreInteractions(iosDevice);
|
||||
verifyNoMoreInteractions(iosAccount);
|
||||
verifyNoMoreInteractions(jedis);
|
||||
verifyNoMoreInteractions(jedisPool);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunkNoDeviceAccount() throws AccountDatabaseCrawlerRestartException {
|
||||
activeUserCounter.onCrawlChunk(Optional.of(NUMBER_NODEVICE), Arrays.asList(noDeviceAccount));
|
||||
|
||||
verify(noDeviceAccount, times(1)).getMasterDevice();
|
||||
|
||||
verify(jedisPool, times(1)).getWriteResource();
|
||||
verify(jedis, times(1)).get(eq(TALLY_KEY));
|
||||
verify(jedis, times(1)).set(any(String.class), eq("{\"fromNumber\":\""+NUMBER_NODEVICE+"\",\"platforms\":{},\"countries\":{}}"));
|
||||
verify(jedis, times(1)).close();
|
||||
|
||||
verify(metricsFactory, times(0)).getReporters();
|
||||
|
||||
verifyZeroInteractions(iosDevice);
|
||||
verifyZeroInteractions(iosAccount);
|
||||
verifyZeroInteractions(androidDevice);
|
||||
verifyZeroInteractions(androidAccount);
|
||||
verifyZeroInteractions(noDeviceAccount);
|
||||
verifyZeroInteractions(metricsFactory);
|
||||
|
||||
verifyNoMoreInteractions(jedis);
|
||||
verifyNoMoreInteractions(jedisPool);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunkMixedAccount() throws AccountDatabaseCrawlerRestartException {
|
||||
activeUserCounter.onCrawlChunk(Optional.of(NUMBER_IOS), Arrays.asList(iosAccount, androidAccount, noDeviceAccount));
|
||||
|
||||
verify(iosAccount, times(1)).getMasterDevice();
|
||||
verify(iosAccount, times(1)).getNumber();
|
||||
verify(androidAccount, times(1)).getMasterDevice();
|
||||
verify(androidAccount, times(1)).getNumber();
|
||||
verify(noDeviceAccount, times(1)).getMasterDevice();
|
||||
|
||||
verify(iosDevice, times(1)).getLastSeen();
|
||||
verify(iosDevice, times(1)).getApnId();
|
||||
verify(iosDevice, times(0)).getGcmId();
|
||||
|
||||
verify(androidDevice, times(1)).getLastSeen();
|
||||
verify(androidDevice, times(1)).getApnId();
|
||||
verify(androidDevice, times(1)).getGcmId();
|
||||
|
||||
verify(jedisPool, times(1)).getWriteResource();
|
||||
verify(jedis, times(1)).get(eq(TALLY_KEY));
|
||||
verify(jedis, times(1)).set(any(String.class), eq("{\"fromNumber\":\""+NUMBER_IOS+"\",\"platforms\":{\"android\":[0,0,0,1,1],\"ios\":[1,1,1,1,1]},\"countries\":{\"55\":[0,0,0,1,1],\"1\":[1,1,1,1,1]}}"));
|
||||
verify(jedis, times(1)).close();
|
||||
|
||||
verify(metricsFactory, times(0)).getReporters();
|
||||
|
||||
verifyZeroInteractions(metricsFactory);
|
||||
|
||||
verifyNoMoreInteractions(iosDevice);
|
||||
verifyNoMoreInteractions(iosAccount);
|
||||
verifyNoMoreInteractions(androidDevice);
|
||||
verifyNoMoreInteractions(androidAccount);
|
||||
verifyNoMoreInteractions(noDeviceAccount);
|
||||
verifyNoMoreInteractions(jedis);
|
||||
verifyNoMoreInteractions(jedisPool);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/**
|
||||
* Copyright (C) 2018 Open WhisperSystems
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import org.whispersystems.textsecuregcm.entities.ClientContact;
|
||||
import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationRequest;
|
||||
import org.whispersystems.textsecuregcm.entities.DirectoryReconciliationResponse;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountDatabaseCrawlerRestartException;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager.BatchOperationHandle;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryManager;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciler;
|
||||
import org.whispersystems.textsecuregcm.storage.DirectoryReconciliationClient;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
public class DirectoryReconcilerTest {
|
||||
private static final String VALID_NUMBER = "valid";
|
||||
private static final String INACTIVE_NUMBER = "inactive";
|
||||
|
||||
private final Account activeAccount = mock(Account.class);
|
||||
private final Account inactiveAccount = mock(Account.class);
|
||||
private final BatchOperationHandle batchOperationHandle = mock(BatchOperationHandle.class);
|
||||
private final DirectoryManager directoryManager = mock(DirectoryManager.class);
|
||||
private final DirectoryReconciliationClient reconciliationClient = mock(DirectoryReconciliationClient.class);
|
||||
private final DirectoryReconciler directoryReconciler = new DirectoryReconciler(reconciliationClient, directoryManager);
|
||||
|
||||
private final DirectoryReconciliationResponse successResponse = new DirectoryReconciliationResponse(DirectoryReconciliationResponse.Status.OK);
|
||||
private final DirectoryReconciliationResponse missingResponse = new DirectoryReconciliationResponse(DirectoryReconciliationResponse.Status.MISSING);
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
when(activeAccount.getNumber()).thenReturn(VALID_NUMBER);
|
||||
when(activeAccount.isActive()).thenReturn(true);
|
||||
when(inactiveAccount.getNumber()).thenReturn(INACTIVE_NUMBER);
|
||||
when(inactiveAccount.isActive()).thenReturn(false);
|
||||
when(directoryManager.startBatchOperation()).thenReturn(batchOperationHandle);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCrawlChunkValid() throws AccountDatabaseCrawlerRestartException {
|
||||
when(reconciliationClient.sendChunk(any())).thenReturn(successResponse);
|
||||
directoryReconciler.onCrawlChunk(Optional.of(VALID_NUMBER), Arrays.asList(activeAccount, inactiveAccount));
|
||||
|
||||
verify(activeAccount, times(2)).getNumber();
|
||||
verify(activeAccount, times(2)).isActive();
|
||||
verify(inactiveAccount, times(2)).getNumber();
|
||||
verify(inactiveAccount, times(2)).isActive();
|
||||
|
||||
ArgumentCaptor<DirectoryReconciliationRequest> request = ArgumentCaptor.forClass(DirectoryReconciliationRequest.class);
|
||||
verify(reconciliationClient, times(1)).sendChunk(request.capture());
|
||||
|
||||
assertThat(request.getValue().getFromNumber()).isEqualTo(VALID_NUMBER);
|
||||
assertThat(request.getValue().getToNumber()).isEqualTo(INACTIVE_NUMBER);
|
||||
assertThat(request.getValue().getNumbers()).isEqualTo(Arrays.asList(VALID_NUMBER));
|
||||
|
||||
ArgumentCaptor<ClientContact> addedContact = ArgumentCaptor.forClass(ClientContact.class);
|
||||
verify(directoryManager, times(1)).startBatchOperation();
|
||||
verify(directoryManager, times(1)).add(eq(batchOperationHandle), addedContact.capture());
|
||||
verify(directoryManager, times(1)).remove(eq(batchOperationHandle), eq(INACTIVE_NUMBER));
|
||||
verify(directoryManager, times(1)).stopBatchOperation(eq(batchOperationHandle));
|
||||
|
||||
assertThat(addedContact.getValue().getToken()).isEqualTo(Util.getContactToken(VALID_NUMBER));
|
||||
|
||||
verifyNoMoreInteractions(activeAccount);
|
||||
verifyNoMoreInteractions(inactiveAccount);
|
||||
verifyNoMoreInteractions(batchOperationHandle);
|
||||
verifyNoMoreInteractions(directoryManager);
|
||||
verifyNoMoreInteractions(reconciliationClient);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.HandleCallback;
|
||||
import org.jdbi.v3.core.HandleConsumer;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.jdbi.v3.core.transaction.SerializableTransactionRunner;
|
||||
import org.jdbi.v3.core.transaction.TransactionException;
|
||||
import org.jdbi.v3.core.transaction.TransactionIsolationLevel;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.postgresql.util.PSQLException;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.PreKey;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.KeyRecord;
|
||||
import org.whispersystems.textsecuregcm.storage.Keys;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.resilience4j.circuitbreaker.CircuitBreakerOpenException;
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class KeysTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private Keys keys;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
FaultTolerantDatabase faultTolerantDatabase = new FaultTolerantDatabase("keysTest",
|
||||
Jdbi.create(db.getTestDatabase()),
|
||||
new CircuitBreakerConfiguration());
|
||||
|
||||
this.keys = new Keys(faultTolerantDatabase);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPopulateKeys() throws SQLException {
|
||||
List<PreKey> deviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> deviceTwoPreKeys = new LinkedList<>();
|
||||
|
||||
List<PreKey> oldAnotherDeviceOnePrKeys = new LinkedList<>();
|
||||
List<PreKey> anotherDeviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> anotherDeviceTwoPreKeys = new LinkedList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
deviceOnePreKeys.add(new PreKey(i, "+14152222222Device1PublicKey" + i));
|
||||
deviceTwoPreKeys.add(new PreKey(i, "+14152222222Device2PublicKey" + i));
|
||||
}
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
oldAnotherDeviceOnePrKeys.add(new PreKey(i, "OldPublicKey" + i));
|
||||
anotherDeviceOnePreKeys.add(new PreKey(i, "+14151111111Device1PublicKey" + i));
|
||||
anotherDeviceTwoPreKeys.add(new PreKey(i, "+14151111111Device2PublicKey" + i));
|
||||
}
|
||||
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
keys.store("+14152222222", 2, deviceTwoPreKeys);
|
||||
|
||||
keys.store("+14151111111", 1, oldAnotherDeviceOnePrKeys);
|
||||
keys.store("+14151111111", 1, anotherDeviceOnePreKeys);
|
||||
keys.store("+14151111111", 2, anotherDeviceTwoPreKeys);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM keys WHERE number = ? AND device_id = ? ORDER BY key_id");
|
||||
verifyStoredState(statement, "+14152222222", 1);
|
||||
verifyStoredState(statement, "+14152222222", 2);
|
||||
verifyStoredState(statement, "+14151111111", 1);
|
||||
verifyStoredState(statement, "+14151111111", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeyCount() {
|
||||
List<PreKey> deviceOnePreKeys = new LinkedList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
deviceOnePreKeys.add(new PreKey(i, "+14152222222Device1PublicKey" + i));
|
||||
}
|
||||
|
||||
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetForDevice() {
|
||||
List<PreKey> deviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> deviceTwoPreKeys = new LinkedList<>();
|
||||
|
||||
List<PreKey> anotherDeviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> anotherDeviceTwoPreKeys = new LinkedList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
deviceOnePreKeys.add(new PreKey(i, "+14152222222Device1PublicKey" + i));
|
||||
deviceTwoPreKeys.add(new PreKey(i, "+14152222222Device2PublicKey" + i));
|
||||
}
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
anotherDeviceOnePreKeys.add(new PreKey(i, "+14151111111Device1PublicKey" + i));
|
||||
anotherDeviceTwoPreKeys.add(new PreKey(i, "+14151111111Device2PublicKey" + i));
|
||||
}
|
||||
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
keys.store("+14152222222", 2, deviceTwoPreKeys);
|
||||
|
||||
keys.store("+14151111111", 1, anotherDeviceOnePreKeys);
|
||||
keys.store("+14151111111", 2, anotherDeviceTwoPreKeys);
|
||||
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(100);
|
||||
List<KeyRecord> records = keys.get("+14152222222", 1);
|
||||
|
||||
assertThat(records.size()).isEqualTo(1);
|
||||
assertThat(records.get(0).getKeyId()).isEqualTo(1);
|
||||
assertThat(records.get(0).getPublicKey()).isEqualTo("+14152222222Device1PublicKey1");
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(99);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14151111111", 1)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14151111111", 2)).isEqualTo(100);
|
||||
|
||||
records = keys.get("+14152222222", 1);
|
||||
|
||||
assertThat(records.size()).isEqualTo(1);
|
||||
assertThat(records.get(0).getKeyId()).isEqualTo(2);
|
||||
assertThat(records.get(0).getPublicKey()).isEqualTo("+14152222222Device1PublicKey2");
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(98);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14151111111", 1)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14151111111", 2)).isEqualTo(100);
|
||||
|
||||
records = keys.get("+14152222222", 2);
|
||||
|
||||
assertThat(records.size()).isEqualTo(1);
|
||||
assertThat(records.get(0).getKeyId()).isEqualTo(1);
|
||||
assertThat(records.get(0).getPublicKey()).isEqualTo("+14152222222Device2PublicKey1");
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(98);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(99);
|
||||
assertThat(keys.getCount("+14151111111", 1)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14151111111", 2)).isEqualTo(100);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetForAllDevices() {
|
||||
List<PreKey> deviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> deviceTwoPreKeys = new LinkedList<>();
|
||||
|
||||
List<PreKey> anotherDeviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> anotherDeviceTwoPreKeys = new LinkedList<>();
|
||||
List<PreKey> anotherDeviceThreePreKeys = new LinkedList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
deviceOnePreKeys.add(new PreKey(i, "+14152222222Device1PublicKey" + i));
|
||||
deviceTwoPreKeys.add(new PreKey(i, "+14152222222Device2PublicKey" + i));
|
||||
}
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
anotherDeviceOnePreKeys.add(new PreKey(i, "+14151111111Device1PublicKey" + i));
|
||||
anotherDeviceTwoPreKeys.add(new PreKey(i, "+14151111111Device2PublicKey" + i));
|
||||
anotherDeviceThreePreKeys.add(new PreKey(i, "+14151111111Device3PublicKey" + i));
|
||||
}
|
||||
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
keys.store("+14152222222", 2, deviceTwoPreKeys);
|
||||
|
||||
keys.store("+14151111111", 1, anotherDeviceOnePreKeys);
|
||||
keys.store("+14151111111", 2, anotherDeviceTwoPreKeys);
|
||||
keys.store("+14151111111", 3, anotherDeviceThreePreKeys);
|
||||
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(100);
|
||||
|
||||
List<KeyRecord> records = keys.get("+14152222222");
|
||||
|
||||
assertThat(records.size()).isEqualTo(2);
|
||||
assertThat(records.get(0).getKeyId()).isEqualTo(1);
|
||||
assertThat(records.get(1).getKeyId()).isEqualTo(1);
|
||||
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14152222222Device1PublicKey1"))).isTrue();
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14152222222Device2PublicKey1"))).isTrue();
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(99);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(99);
|
||||
|
||||
records = keys.get("+14152222222");
|
||||
|
||||
assertThat(records.size()).isEqualTo(2);
|
||||
assertThat(records.get(0).getKeyId()).isEqualTo(2);
|
||||
assertThat(records.get(1).getKeyId()).isEqualTo(2);
|
||||
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14152222222Device1PublicKey2"))).isTrue();
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14152222222Device2PublicKey2"))).isTrue();
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(98);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(98);
|
||||
|
||||
|
||||
records = keys.get("+14151111111");
|
||||
|
||||
assertThat(records.size()).isEqualTo(3);
|
||||
assertThat(records.get(0).getKeyId()).isEqualTo(1);
|
||||
assertThat(records.get(1).getKeyId()).isEqualTo(1);
|
||||
assertThat(records.get(2).getKeyId()).isEqualTo(1);
|
||||
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14151111111Device1PublicKey1"))).isTrue();
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14151111111Device2PublicKey1"))).isTrue();
|
||||
assertThat(records.stream().anyMatch(record -> record.getPublicKey().equals("+14151111111Device3PublicKey1"))).isTrue();
|
||||
|
||||
assertThat(keys.getCount("+14151111111", 1)).isEqualTo(99);
|
||||
assertThat(keys.getCount("+14151111111", 2)).isEqualTo(99);
|
||||
assertThat(keys.getCount("+14151111111", 3)).isEqualTo(99);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetForAllDevicesParallel() throws InterruptedException {
|
||||
List<PreKey> deviceOnePreKeys = new LinkedList<>();
|
||||
List<PreKey> deviceTwoPreKeys = new LinkedList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
deviceOnePreKeys.add(new PreKey(i, "+14152222222Device1PublicKey" + i));
|
||||
deviceTwoPreKeys.add(new PreKey(i, "+14152222222Device2PublicKey" + i));
|
||||
}
|
||||
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
keys.store("+14152222222", 2, deviceTwoPreKeys);
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(100);
|
||||
assertThat(keys.getCount("+14152222222", 2)).isEqualTo(100);
|
||||
|
||||
List<Thread> threads = new LinkedList<>();
|
||||
|
||||
for (int i=0;i<20;i++) {
|
||||
Thread thread = new Thread(() -> {
|
||||
List<KeyRecord> results = keys.get("+14152222222");
|
||||
assertThat(results.size()).isEqualTo(2);
|
||||
});
|
||||
thread.start();
|
||||
threads.add(thread);
|
||||
}
|
||||
|
||||
for (Thread thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
|
||||
assertThat(keys.getCount("+14152222222", 1)).isEqualTo(80);
|
||||
assertThat(keys.getCount("+14152222222",2)).isEqualTo(80);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testEmptyKeyGet() {
|
||||
List<KeyRecord> records = keys.get("+14152222222");
|
||||
|
||||
assertThat(records.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVacuum() {
|
||||
keys.vacuum();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBreaker() throws InterruptedException {
|
||||
Jdbi jdbi = mock(Jdbi.class);
|
||||
doThrow(new TransactionException("Database error!")).when(jdbi).useTransaction(any(TransactionIsolationLevel.class), any(HandleConsumer.class));
|
||||
when(jdbi.getConfig(any())).thenReturn(mock(SerializableTransactionRunner.Configuration.class));
|
||||
|
||||
CircuitBreakerConfiguration configuration = new CircuitBreakerConfiguration();
|
||||
configuration.setWaitDurationInOpenStateInSeconds(1);
|
||||
configuration.setRingBufferSizeInHalfOpenState(1);
|
||||
configuration.setRingBufferSizeInClosedState(2);
|
||||
configuration.setFailureRateThreshold(50);
|
||||
|
||||
Keys keys = new Keys(new FaultTolerantDatabase("testBreaker", jdbi, configuration));
|
||||
|
||||
List<PreKey> deviceOnePreKeys = new LinkedList<>();
|
||||
|
||||
for (int i=1;i<=100;i++) {
|
||||
deviceOnePreKeys.add(new PreKey(i, "+14152222222Device1PublicKey" + i));
|
||||
}
|
||||
|
||||
try {
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
try {
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
throw new AssertionError();
|
||||
} catch (CircuitBreakerOpenException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
Thread.sleep(1100);
|
||||
|
||||
try {
|
||||
keys.store("+14152222222", 1, deviceOnePreKeys);
|
||||
throw new AssertionError();
|
||||
} catch (TransactionException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private void verifyStoredState(PreparedStatement statement, String number, int deviceId) throws SQLException {
|
||||
statement.setString(1, number);
|
||||
statement.setInt(2, deviceId);
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
int rowCount = 1;
|
||||
|
||||
while (resultSet.next()) {
|
||||
long keyId = resultSet.getLong("key_id");
|
||||
String publicKey = resultSet.getString("public_key");
|
||||
|
||||
|
||||
assertThat(keyId).isEqualTo(rowCount);
|
||||
assertThat(publicKey).isEqualTo(number + "Device" + deviceId + "PublicKey" + rowCount);
|
||||
|
||||
rowCount++;
|
||||
}
|
||||
|
||||
resultSet.close();
|
||||
|
||||
assertThat(rowCount).isEqualTo(101);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.Messages;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class MessagesTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("messagedb.xml"));
|
||||
|
||||
private Messages messages;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
this.messages = new Messages(new FaultTolerantDatabase("messages-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStore() throws SQLException {
|
||||
Envelope envelope = generateEnvelope();
|
||||
UUID guid = UUID.randomUUID();
|
||||
|
||||
messages.store(guid, envelope, "+14151112222", 1);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM messages WHERE destination = ?");
|
||||
statement.setString(1, "+14151112222");
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
assertThat(resultSet.next()).isTrue();
|
||||
|
||||
assertThat(resultSet.getString("guid")).isEqualTo(guid.toString());
|
||||
assertThat(resultSet.getInt("type")).isEqualTo(envelope.getType().getNumber());
|
||||
assertThat(resultSet.getString("relay")).isNullOrEmpty();
|
||||
assertThat(resultSet.getLong("timestamp")).isEqualTo(envelope.getTimestamp());
|
||||
assertThat(resultSet.getLong("server_timestamp")).isEqualTo(envelope.getServerTimestamp());
|
||||
assertThat(resultSet.getString("source")).isEqualTo(envelope.getSource());
|
||||
assertThat(resultSet.getLong("source_device")).isEqualTo(envelope.getSourceDevice());
|
||||
assertThat(resultSet.getBytes("message")).isEqualTo(envelope.getLegacyMessage().toByteArray());
|
||||
assertThat(resultSet.getBytes("content")).isEqualTo(envelope.getContent().toByteArray());
|
||||
assertThat(resultSet.getString("destination")).isEqualTo("+14151112222");
|
||||
assertThat(resultSet.getLong("destination_device")).isEqualTo(1);
|
||||
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoad() {
|
||||
List<MessageToStore> inserted = new ArrayList<>(50);
|
||||
|
||||
for (int i=0;i<50;i++) {
|
||||
MessageToStore message = generateMessageToStore();
|
||||
inserted.add(message);
|
||||
|
||||
messages.store(message.guid, message.envelope, "+14151112222", 1);
|
||||
}
|
||||
|
||||
inserted.sort(Comparator.comparingLong(o -> o.envelope.getTimestamp()));
|
||||
|
||||
List<OutgoingMessageEntity> retrieved = messages.load("+14151112222", 1);
|
||||
|
||||
assertThat(retrieved.size()).isEqualTo(inserted.size());
|
||||
|
||||
for (int i=0;i<retrieved.size();i++) {
|
||||
verifyExpected(retrieved.get(i), inserted.get(i).envelope, inserted.get(i).guid);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeBySourceDestinationTimestamp() {
|
||||
List<MessageToStore> inserted = insertRandom("+14151112222", 1);
|
||||
List<MessageToStore> unrelated = insertRandom("+14151114444", 3);
|
||||
MessageToStore toRemove = inserted.remove(new Random(System.currentTimeMillis()).nextInt(inserted.size() - 1));
|
||||
Optional<OutgoingMessageEntity> removed = messages.remove("+14151112222", 1, toRemove.envelope.getSource(), toRemove.envelope.getTimestamp());
|
||||
|
||||
assertThat(removed.isPresent()).isTrue();
|
||||
verifyExpected(removed.get(), toRemove.envelope, toRemove.guid);
|
||||
|
||||
verifyInTact(inserted, "+14151112222", 1);
|
||||
verifyInTact(unrelated, "+14151114444", 3);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeByDestinationGuid() {
|
||||
List<MessageToStore> unrelated = insertRandom("+14151113333", 2);
|
||||
List<MessageToStore> inserted = insertRandom("+14151112222", 1);
|
||||
MessageToStore toRemove = inserted.remove(new Random(System.currentTimeMillis()).nextInt(inserted.size() - 1));
|
||||
Optional<OutgoingMessageEntity> removed = messages.remove("+14151112222", toRemove.guid);
|
||||
|
||||
assertThat(removed.isPresent()).isTrue();
|
||||
verifyExpected(removed.get(), toRemove.envelope, toRemove.guid);
|
||||
|
||||
verifyInTact(inserted, "+14151112222", 1);
|
||||
verifyInTact(unrelated, "+14151113333", 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeByDestinationRowId() {
|
||||
List<MessageToStore> unrelatedInserted = insertRandom("+14151111111", 1);
|
||||
List<MessageToStore> inserted = insertRandom("+14151112222", 1);
|
||||
|
||||
inserted.sort(Comparator.comparingLong(o -> o.envelope.getTimestamp()));
|
||||
|
||||
List<OutgoingMessageEntity> retrieved = messages.load("+14151112222", 1);
|
||||
|
||||
int toRemoveIndex = new Random(System.currentTimeMillis()).nextInt(inserted.size() - 1);
|
||||
|
||||
inserted.remove(toRemoveIndex);
|
||||
|
||||
messages.remove("+14151112222", retrieved.get(toRemoveIndex).getId());
|
||||
|
||||
verifyInTact(inserted, "+14151112222", 1);
|
||||
verifyInTact(unrelatedInserted, "+14151111111", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLoadEmpty() {
|
||||
List<MessageToStore> inserted = insertRandom("+14151112222", 1);
|
||||
List<OutgoingMessageEntity> loaded = messages.load("+14159999999", 1);
|
||||
assertThat(loaded.isEmpty()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearDestination() {
|
||||
insertRandom("+14151112222", 1);
|
||||
insertRandom("+14151112222", 2);
|
||||
|
||||
List<MessageToStore> unrelated = insertRandom("+14151111111", 1);
|
||||
|
||||
messages.clear("+14151112222");
|
||||
|
||||
assertThat(messages.load("+14151112222", 1).isEmpty()).isTrue();
|
||||
|
||||
verifyInTact(unrelated, "+14151111111", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClearDestinationDevice() {
|
||||
insertRandom("+14151112222", 1);
|
||||
List<MessageToStore> inserted = insertRandom("+14151112222", 2);
|
||||
|
||||
List<MessageToStore> unrelated = insertRandom("+14151111111", 1);
|
||||
|
||||
messages.clear("+14151112222", 1);
|
||||
|
||||
assertThat(messages.load("+14151112222", 1).isEmpty()).isTrue();
|
||||
|
||||
verifyInTact(inserted, "+14151112222", 2);
|
||||
verifyInTact(unrelated, "+14151111111", 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVacuum() {
|
||||
List<MessageToStore> inserted = insertRandom("+14151112222", 2);
|
||||
messages.vacuum();
|
||||
verifyInTact(inserted, "+14151112222", 2);
|
||||
}
|
||||
|
||||
private List<MessageToStore> insertRandom(String destination, int destinationDevice) {
|
||||
List<MessageToStore> inserted = new ArrayList<>(50);
|
||||
|
||||
for (int i=0;i<50;i++) {
|
||||
MessageToStore message = generateMessageToStore();
|
||||
inserted.add(message);
|
||||
|
||||
messages.store(message.guid, message.envelope, destination, destinationDevice);
|
||||
}
|
||||
|
||||
return inserted;
|
||||
}
|
||||
|
||||
private void verifyInTact(List<MessageToStore> inserted, String destination, int destinationDevice) {
|
||||
inserted.sort(Comparator.comparingLong(o -> o.envelope.getTimestamp()));
|
||||
|
||||
List<OutgoingMessageEntity> retrieved = messages.load(destination, destinationDevice);
|
||||
|
||||
assertThat(retrieved.size()).isEqualTo(inserted.size());
|
||||
|
||||
for (int i=0;i<retrieved.size();i++) {
|
||||
verifyExpected(retrieved.get(i), inserted.get(i).envelope, inserted.get(i).guid);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void verifyExpected(OutgoingMessageEntity retrieved, Envelope inserted, UUID guid) {
|
||||
assertThat(retrieved.getTimestamp()).isEqualTo(inserted.getTimestamp());
|
||||
assertThat(retrieved.getSource()).isEqualTo(inserted.getSource());
|
||||
assertThat(retrieved.getRelay()).isEqualTo(inserted.getRelay());
|
||||
assertThat(retrieved.getType()).isEqualTo(inserted.getType().getNumber());
|
||||
assertThat(retrieved.getContent()).isEqualTo(inserted.getContent().toByteArray());
|
||||
assertThat(retrieved.getMessage()).isEqualTo(inserted.getLegacyMessage().toByteArray());
|
||||
assertThat(retrieved.getServerTimestamp()).isEqualTo(inserted.getServerTimestamp());
|
||||
assertThat(retrieved.getGuid()).isEqualTo(guid);
|
||||
assertThat(retrieved.getSourceDevice()).isEqualTo(inserted.getSourceDevice());
|
||||
}
|
||||
|
||||
private MessageToStore generateMessageToStore() {
|
||||
return new MessageToStore(UUID.randomUUID(), generateEnvelope());
|
||||
}
|
||||
|
||||
private Envelope generateEnvelope() {
|
||||
Random random = new Random();
|
||||
byte[] content = new byte[256];
|
||||
byte[] legacy = new byte[200];
|
||||
|
||||
Arrays.fill(content, (byte)random.nextInt(255));
|
||||
Arrays.fill(legacy, (byte)random.nextInt(255));
|
||||
|
||||
return Envelope.newBuilder()
|
||||
.setSourceDevice(random.nextInt(10000))
|
||||
.setSource("testSource" + random.nextInt())
|
||||
.setTimestamp(random.nextInt(100000))
|
||||
.setServerTimestamp(random.nextInt(100000))
|
||||
.setLegacyMessage(ByteString.copyFrom(legacy))
|
||||
.setContent(ByteString.copyFrom(content))
|
||||
.setType(Envelope.Type.CIPHERTEXT)
|
||||
.setServerGuid(UUID.randomUUID().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class MessageToStore {
|
||||
private final UUID guid;
|
||||
private final Envelope envelope;
|
||||
|
||||
private MessageToStore(UUID guid, Envelope envelope) {
|
||||
this.guid = guid;
|
||||
this.envelope = envelope;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingAccounts;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class PendingAccountsTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private PendingAccounts pendingAccounts;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
this.pendingAccounts = new PendingAccounts(new FaultTolerantDatabase("pending_accounts-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStore() throws SQLException {
|
||||
pendingAccounts.insert("+14151112222", "1234", 1111);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM pending_accounts WHERE number = ?");
|
||||
statement.setString(1, "+14151112222");
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
if (resultSet.next()) {
|
||||
assertThat(resultSet.getString("verification_code")).isEqualTo("1234");
|
||||
assertThat(resultSet.getLong("timestamp")).isEqualTo(1111);
|
||||
} else {
|
||||
throw new AssertionError("no results");
|
||||
}
|
||||
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieve() throws Exception {
|
||||
pendingAccounts.insert("+14151112222", "4321", 2222);
|
||||
pendingAccounts.insert("+14151113333", "1212", 5555);
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingAccounts.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4321");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(2222);
|
||||
|
||||
Optional<StoredVerificationCode> missingCode = pendingAccounts.getCodeForNumber("+11111111111");
|
||||
assertThat(missingCode.isPresent()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverwrite() throws Exception {
|
||||
pendingAccounts.insert("+14151112222", "4321", 2222);
|
||||
pendingAccounts.insert("+14151112222", "4444", 3333);
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingAccounts.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4444");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(3333);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVacuum() {
|
||||
pendingAccounts.insert("+14151112222", "4321", 2222);
|
||||
pendingAccounts.insert("+14151112222", "4444", 3333);
|
||||
pendingAccounts.vacuum();
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingAccounts.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4444");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(3333);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
pendingAccounts.insert("+14151112222", "4321", 2222);
|
||||
pendingAccounts.insert("+14151113333", "1212", 5555);
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingAccounts.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4321");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(2222);
|
||||
|
||||
pendingAccounts.remove("+14151112222");
|
||||
|
||||
verificationCode = pendingAccounts.getCodeForNumber("+14151112222");
|
||||
assertThat(verificationCode.isPresent()).isFalse();
|
||||
|
||||
verificationCode = pendingAccounts.getCodeForNumber("+14151113333");
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("1212");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(5555);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.opentable.db.postgres.embedded.LiquibasePreparer;
|
||||
import com.opentable.db.postgres.junit.EmbeddedPostgresRules;
|
||||
import com.opentable.db.postgres.junit.PreparedDbRule;
|
||||
import org.jdbi.v3.core.Jdbi;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
|
||||
import org.whispersystems.textsecuregcm.configuration.CircuitBreakerConfiguration;
|
||||
import org.whispersystems.textsecuregcm.storage.FaultTolerantDatabase;
|
||||
import org.whispersystems.textsecuregcm.storage.PendingDevices;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class PendingDevicesTest {
|
||||
|
||||
@Rule
|
||||
public PreparedDbRule db = EmbeddedPostgresRules.preparedDatabase(LiquibasePreparer.forClasspathLocation("accountsdb.xml"));
|
||||
|
||||
private PendingDevices pendingDevices;
|
||||
|
||||
@Before
|
||||
public void setupAccountsDao() {
|
||||
this.pendingDevices = new PendingDevices(new FaultTolerantDatabase("peding_devices-test", Jdbi.create(db.getTestDatabase()), new CircuitBreakerConfiguration()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStore() throws SQLException {
|
||||
pendingDevices.insert("+14151112222", "1234", 1111);
|
||||
|
||||
PreparedStatement statement = db.getTestDatabase().getConnection().prepareStatement("SELECT * FROM pending_devices WHERE number = ?");
|
||||
statement.setString(1, "+14151112222");
|
||||
|
||||
ResultSet resultSet = statement.executeQuery();
|
||||
|
||||
if (resultSet.next()) {
|
||||
assertThat(resultSet.getString("verification_code")).isEqualTo("1234");
|
||||
assertThat(resultSet.getLong("timestamp")).isEqualTo(1111);
|
||||
} else {
|
||||
throw new AssertionError("no results");
|
||||
}
|
||||
|
||||
assertThat(resultSet.next()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRetrieve() throws Exception {
|
||||
pendingDevices.insert("+14151112222", "4321", 2222);
|
||||
pendingDevices.insert("+14151113333", "1212", 5555);
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingDevices.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4321");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(2222);
|
||||
|
||||
Optional<StoredVerificationCode> missingCode = pendingDevices.getCodeForNumber("+11111111111");
|
||||
assertThat(missingCode.isPresent()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOverwrite() throws Exception {
|
||||
pendingDevices.insert("+14151112222", "4321", 2222);
|
||||
pendingDevices.insert("+14151112222", "4444", 3333);
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingDevices.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4444");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(3333);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
pendingDevices.insert("+14151112222", "4321", 2222);
|
||||
pendingDevices.insert("+14151113333", "1212", 5555);
|
||||
|
||||
Optional<StoredVerificationCode> verificationCode = pendingDevices.getCodeForNumber("+14151112222");
|
||||
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("4321");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(2222);
|
||||
|
||||
pendingDevices.remove("+14151112222");
|
||||
|
||||
verificationCode = pendingDevices.getCodeForNumber("+14151112222");
|
||||
assertThat(verificationCode.isPresent()).isFalse();
|
||||
|
||||
verificationCode = pendingDevices.getCodeForNumber("+14151113333");
|
||||
assertThat(verificationCode.isPresent()).isTrue();
|
||||
assertThat(verificationCode.get().getCode()).isEqualTo("1212");
|
||||
assertThat(verificationCode.get().getTimestamp()).isEqualTo(5555);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package org.whispersystems.textsecuregcm.tests.storage;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.entities.SignedPreKey;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.PublicAccount;
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
import static junit.framework.TestCase.assertEquals;
|
||||
import static junit.framework.TestCase.assertNull;
|
||||
|
||||
public class PublicAccountTest {
|
||||
|
||||
@Test
|
||||
public void testPinSanitation() throws IOException {
|
||||
Set<Device> devices = Collections.singleton(new Device(1, "foo", "bar", "12345", null, "gcm-1234", null, null, true, 1234, new SignedPreKey(1, "public-foo", "signature-foo"), 31337, 31336, "Android4Life", true));
|
||||
Account account = new Account("+14151231234", devices, new byte[16]);
|
||||
account.setPin("123456");
|
||||
|
||||
PublicAccount publicAccount = new PublicAccount(account);
|
||||
|
||||
String serialized = SystemMapper.getMapper().writeValueAsString(publicAccount);
|
||||
JsonNode result = SystemMapper.getMapper().readTree(serialized);
|
||||
|
||||
assertEquals("******", result.get("pin").textValue());
|
||||
assertNull(result.get("number"));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.auth.AuthenticationCredentials;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import io.dropwizard.auth.AuthDynamicFeature;
|
||||
import io.dropwizard.auth.basic.BasicCredentialAuthFilter;
|
||||
import static org.mockito.Matchers.anyLong;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class AuthHelper {
|
||||
public static final String VALID_NUMBER = "+14150000000";
|
||||
public static final String VALID_PASSWORD = "foo";
|
||||
|
||||
public static final String VALID_NUMBER_TWO = "+201511111110";
|
||||
public static final String VALID_PASSWORD_TWO = "baz";
|
||||
|
||||
public static final String INVVALID_NUMBER = "+14151111111";
|
||||
public static final String INVALID_PASSWORD = "bar";
|
||||
|
||||
public static final String VALID_IDENTITY = "BcxxDU9FGMda70E7+Uvm7pnQcEdXQ64aJCpPUeRSfcFo";
|
||||
|
||||
public static AccountsManager ACCOUNTS_MANAGER = mock(AccountsManager.class );
|
||||
public static Account VALID_ACCOUNT = mock(Account.class );
|
||||
public static Account VALID_ACCOUNT_TWO = mock(Account.class);
|
||||
public static Device VALID_DEVICE = mock(Device.class );
|
||||
public static Device VALID_DEVICE_TWO = mock(Device.class);
|
||||
private static AuthenticationCredentials VALID_CREDENTIALS = mock(AuthenticationCredentials.class);
|
||||
private static AuthenticationCredentials VALID_CREDENTIALS_TWO = mock(AuthenticationCredentials.class);
|
||||
|
||||
public static AuthDynamicFeature getAuthFilter() {
|
||||
when(VALID_CREDENTIALS.verify("foo")).thenReturn(true);
|
||||
when(VALID_CREDENTIALS_TWO.verify("baz")).thenReturn(true);
|
||||
when(VALID_DEVICE.getAuthenticationCredentials()).thenReturn(VALID_CREDENTIALS);
|
||||
when(VALID_DEVICE_TWO.getAuthenticationCredentials()).thenReturn(VALID_CREDENTIALS_TWO);
|
||||
when(VALID_DEVICE.isMaster()).thenReturn(true);
|
||||
when(VALID_DEVICE_TWO.isMaster()).thenReturn(true);
|
||||
when(VALID_DEVICE.getId()).thenReturn(1L);
|
||||
when(VALID_DEVICE_TWO.getId()).thenReturn(1L);
|
||||
when(VALID_ACCOUNT.getDevice(1L)).thenReturn(Optional.of(VALID_DEVICE));
|
||||
when(VALID_ACCOUNT_TWO.getDevice(eq(1L))).thenReturn(Optional.of(VALID_DEVICE_TWO));
|
||||
when(VALID_ACCOUNT_TWO.getActiveDeviceCount()).thenReturn(6);
|
||||
when(VALID_ACCOUNT.getNumber()).thenReturn(VALID_NUMBER);
|
||||
when(VALID_ACCOUNT_TWO.getNumber()).thenReturn(VALID_NUMBER_TWO);
|
||||
when(VALID_ACCOUNT.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE));
|
||||
when(VALID_ACCOUNT_TWO.getAuthenticatedDevice()).thenReturn(Optional.of(VALID_DEVICE_TWO));
|
||||
when(VALID_ACCOUNT.getRelay()).thenReturn(Optional.<String>empty());
|
||||
when(VALID_ACCOUNT_TWO.getRelay()).thenReturn(Optional.<String>empty());
|
||||
when(VALID_ACCOUNT.isActive()).thenReturn(true);
|
||||
when(VALID_ACCOUNT_TWO.isActive()).thenReturn(true);
|
||||
when(VALID_ACCOUNT.getIdentityKey()).thenReturn(VALID_IDENTITY);
|
||||
when(ACCOUNTS_MANAGER.get(VALID_NUMBER)).thenReturn(Optional.of(VALID_ACCOUNT));
|
||||
when(ACCOUNTS_MANAGER.get(VALID_NUMBER_TWO)).thenReturn(Optional.of(VALID_ACCOUNT_TWO));
|
||||
|
||||
return new AuthDynamicFeature(new BasicCredentialAuthFilter.Builder<Account>()
|
||||
.setAuthenticator(new AccountAuthenticator(ACCOUNTS_MANAGER))
|
||||
.buildAuthFilter());
|
||||
}
|
||||
|
||||
public static String getAuthHeader(String number, String password) {
|
||||
return "Basic " + Base64.encodeBytes((number + ":" + password).getBytes());
|
||||
}
|
||||
|
||||
public static String getUnidentifiedAccessHeader(byte[] key) {
|
||||
return Base64.encodeBytes(key);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.util.BlockingThreadPoolExecutor;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class BlockingThreadPoolExecutorTest {
|
||||
|
||||
@Test
|
||||
public void testBlocking() {
|
||||
BlockingThreadPoolExecutor executor = new BlockingThreadPoolExecutor(1, 3);
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.sleep(1000);
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(System.currentTimeMillis() - start < 500);
|
||||
start = System.currentTimeMillis();
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.sleep(1000);
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(System.currentTimeMillis() - start < 500);
|
||||
|
||||
start = System.currentTimeMillis();
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.sleep(1000);
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(System.currentTimeMillis() - start < 500);
|
||||
|
||||
start = System.currentTimeMillis();
|
||||
|
||||
executor.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Util.sleep(1000);
|
||||
}
|
||||
});
|
||||
|
||||
assertTrue(System.currentTimeMillis() - start > 500);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonParseException;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import org.whispersystems.textsecuregcm.util.SystemMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static io.dropwizard.testing.FixtureHelpers.fixture;
|
||||
|
||||
public class JsonHelpers {
|
||||
|
||||
private static final ObjectMapper objectMapper = SystemMapper.getMapper();
|
||||
|
||||
public static String asJson(Object object) throws JsonProcessingException {
|
||||
return objectMapper.writeValueAsString(object);
|
||||
}
|
||||
|
||||
public static <T> T fromJson(String value, Class<T> clazz) throws IOException {
|
||||
return objectMapper.readValue(value, clazz);
|
||||
}
|
||||
|
||||
public static String jsonFixture(String filename) throws IOException {
|
||||
return objectMapper.writeValueAsString(objectMapper.readValue(fixture(filename), JsonNode.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
|
||||
|
||||
public class NumberPrefixTest {
|
||||
|
||||
@Test
|
||||
public void testPrefixes() {
|
||||
assertThat(Util.getNumberPrefix("+14151234567")).isEqualTo("+14151");
|
||||
assertThat(Util.getNumberPrefix("+22587654321")).isEqualTo("+2258765");
|
||||
assertThat(Util.getNumberPrefix("+298654321")).isEqualTo("+2986543");
|
||||
assertThat(Util.getNumberPrefix("+12")).isEqualTo("+12");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public class SynchronousExecutorService implements ExecutorService {
|
||||
|
||||
private boolean shutdown = false;
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
shutdown = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Runnable> shutdownNow() {
|
||||
shutdown = true;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isShutdown() {
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTerminated() {
|
||||
return shutdown;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Callable<T> task) {
|
||||
SettableFuture<T> future = null;
|
||||
try {
|
||||
future = SettableFuture.create();
|
||||
future.set(task.call());
|
||||
} catch (Throwable e) {
|
||||
future.setException(e);
|
||||
}
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> Future<T> submit(Runnable task, T result) {
|
||||
SettableFuture<T> future = SettableFuture.create();
|
||||
task.run();
|
||||
|
||||
future.set(result);
|
||||
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Future<?> submit(Runnable task) {
|
||||
SettableFuture future = SettableFuture.create();
|
||||
task.run();
|
||||
future.set(null);
|
||||
return future;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
|
||||
List<Future<T>> results = new LinkedList<>();
|
||||
for (Callable<T> callable : tasks) {
|
||||
SettableFuture<T> future = SettableFuture.create();
|
||||
try {
|
||||
future.set(callable.call());
|
||||
} catch (Throwable e) {
|
||||
future.setException(e);
|
||||
}
|
||||
results.add(future);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
|
||||
return invokeAll(tasks);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Runnable command) {
|
||||
command.run();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import com.amazonaws.HttpMethod;
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.s3.UrlSigner;
|
||||
|
||||
import java.net.URL;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class UrlSignerTest {
|
||||
|
||||
@Test
|
||||
public void testTransferAcceleration() {
|
||||
UrlSigner signer = new UrlSigner("foo", "bar", "attachments-test");
|
||||
URL url = signer.getPreSignedUrl(1234, HttpMethod.GET, false);
|
||||
|
||||
assertThat(url).hasHost("attachments-test.s3-accelerate.amazonaws.com");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTransferUnaccelerated() {
|
||||
UrlSigner signer = new UrlSigner("foo", "bar", "attachments-test");
|
||||
URL url = signer.getPreSignedUrl(1234, HttpMethod.GET, true);
|
||||
|
||||
assertThat(url).hasHost("s3.amazonaws.com");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.whispersystems.textsecuregcm.tests.util;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.whispersystems.textsecuregcm.util.Util;
|
||||
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
|
||||
public class ValidNumberTest {
|
||||
|
||||
@Test
|
||||
public void testValidE164() {
|
||||
assertTrue(Util.isValidNumber("+14151231234"));
|
||||
assertTrue(Util.isValidNumber("+71234567890"));
|
||||
assertTrue(Util.isValidNumber("+447535742222"));
|
||||
assertTrue(Util.isValidNumber("+4915174108888"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidE164() {
|
||||
assertFalse(Util.isValidNumber("+141512312341"));
|
||||
assertFalse(Util.isValidNumber("+712345678901"));
|
||||
assertFalse(Util.isValidNumber("+4475357422221"));
|
||||
assertFalse(Util.isValidNumber("+491517410888811111"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNotE164() {
|
||||
assertFalse(Util.isValidNumber("+1 415 123 1234"));
|
||||
assertFalse(Util.isValidNumber("+1 (415) 123-1234"));
|
||||
assertFalse(Util.isValidNumber("+1 415)123-1234"));
|
||||
assertFalse(Util.isValidNumber("71234567890"));
|
||||
assertFalse(Util.isValidNumber("001447535742222"));
|
||||
assertFalse(Util.isValidNumber(" +14151231234"));
|
||||
assertFalse(Util.isValidNumber("+1415123123a"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortRegions() {
|
||||
assertTrue(Util.isValidNumber("+298123456"));
|
||||
assertTrue(Util.isValidNumber("+299123456"));
|
||||
assertTrue(Util.isValidNumber("+376123456"));
|
||||
assertTrue(Util.isValidNumber("+68512345"));
|
||||
assertTrue(Util.isValidNumber("+689123456"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
package org.whispersystems.textsecuregcm.tests.websocket;
|
||||
|
||||
import com.google.common.util.concurrent.SettableFuture;
|
||||
import com.google.protobuf.ByteString;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentMatchers;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
import org.whispersystems.textsecuregcm.auth.AccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntity;
|
||||
import org.whispersystems.textsecuregcm.entities.OutgoingMessageEntityList;
|
||||
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
|
||||
import org.whispersystems.textsecuregcm.push.PushSender;
|
||||
import org.whispersystems.textsecuregcm.push.ReceiptSender;
|
||||
import org.whispersystems.textsecuregcm.push.WebsocketSender;
|
||||
import org.whispersystems.textsecuregcm.storage.Account;
|
||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||
import org.whispersystems.textsecuregcm.storage.Device;
|
||||
import org.whispersystems.textsecuregcm.storage.MessagesManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PubSubManager;
|
||||
import org.whispersystems.textsecuregcm.storage.PubSubProtos;
|
||||
import org.whispersystems.textsecuregcm.util.Base64;
|
||||
import org.whispersystems.textsecuregcm.websocket.AuthenticatedConnectListener;
|
||||
import org.whispersystems.textsecuregcm.websocket.WebSocketAccountAuthenticator;
|
||||
import org.whispersystems.textsecuregcm.websocket.WebSocketConnection;
|
||||
import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
|
||||
import org.whispersystems.websocket.WebSocketClient;
|
||||
import org.whispersystems.websocket.auth.WebSocketAuthenticator;
|
||||
import org.whispersystems.websocket.auth.WebSocketAuthenticator.AuthenticationResult;
|
||||
import org.whispersystems.websocket.messages.WebSocketResponseMessage;
|
||||
import org.whispersystems.websocket.session.WebSocketSessionContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import io.dropwizard.auth.basic.BasicCredentials;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.*;
|
||||
import static org.whispersystems.textsecuregcm.entities.MessageProtos.Envelope;
|
||||
|
||||
public class WebSocketConnectionTest {
|
||||
|
||||
private static final String VALID_USER = "+14152222222";
|
||||
private static final String INVALID_USER = "+14151111111";
|
||||
|
||||
private static final String VALID_PASSWORD = "secure";
|
||||
private static final String INVALID_PASSWORD = "insecure";
|
||||
|
||||
private static final AccountAuthenticator accountAuthenticator = mock(AccountAuthenticator.class);
|
||||
private static final AccountsManager accountsManager = mock(AccountsManager.class);
|
||||
private static final PubSubManager pubSubManager = mock(PubSubManager.class );
|
||||
private static final Account account = mock(Account.class );
|
||||
private static final Device device = mock(Device.class );
|
||||
private static final UpgradeRequest upgradeRequest = mock(UpgradeRequest.class );
|
||||
private static final PushSender pushSender = mock(PushSender.class);
|
||||
private static final ReceiptSender receiptSender = mock(ReceiptSender.class);
|
||||
private static final ApnFallbackManager apnFallbackManager = mock(ApnFallbackManager.class);
|
||||
|
||||
@Test
|
||||
public void testCredentials() throws Exception {
|
||||
MessagesManager storedMessages = mock(MessagesManager.class);
|
||||
WebSocketAccountAuthenticator webSocketAuthenticator = new WebSocketAccountAuthenticator(accountAuthenticator);
|
||||
AuthenticatedConnectListener connectListener = new AuthenticatedConnectListener(pushSender, receiptSender, storedMessages, pubSubManager, apnFallbackManager);
|
||||
WebSocketSessionContext sessionContext = mock(WebSocketSessionContext.class);
|
||||
|
||||
when(accountAuthenticator.authenticate(eq(new BasicCredentials(VALID_USER, VALID_PASSWORD))))
|
||||
.thenReturn(Optional.of(account));
|
||||
|
||||
when(accountAuthenticator.authenticate(eq(new BasicCredentials(INVALID_USER, INVALID_PASSWORD))))
|
||||
.thenReturn(Optional.<Account>empty());
|
||||
|
||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
||||
|
||||
when(upgradeRequest.getParameterMap()).thenReturn(new HashMap<String, List<String>>() {{
|
||||
put("login", new LinkedList<String>() {{add(VALID_USER);}});
|
||||
put("password", new LinkedList<String>() {{add(VALID_PASSWORD);}});
|
||||
}});
|
||||
|
||||
AuthenticationResult<Account> account = webSocketAuthenticator.authenticate(upgradeRequest);
|
||||
when(sessionContext.getAuthenticated(Account.class)).thenReturn(account.getUser().orElse(null));
|
||||
|
||||
connectListener.onWebSocketConnect(sessionContext);
|
||||
|
||||
verify(sessionContext).addListener(any(WebSocketSessionContext.WebSocketEventListener.class));
|
||||
|
||||
when(upgradeRequest.getParameterMap()).thenReturn(new HashMap<String, List<String>>() {{
|
||||
put("login", new LinkedList<String>() {{add(INVALID_USER);}});
|
||||
put("password", new LinkedList<String>() {{add(INVALID_PASSWORD);}});
|
||||
}});
|
||||
|
||||
account = webSocketAuthenticator.authenticate(upgradeRequest);
|
||||
assertFalse(account.getUser().isPresent());
|
||||
assertTrue(account.isRequired());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpen() throws Exception {
|
||||
MessagesManager storedMessages = mock(MessagesManager.class);
|
||||
|
||||
List<OutgoingMessageEntity> outgoingMessages = new LinkedList<OutgoingMessageEntity> () {{
|
||||
add(createMessage(1L, false, "sender1", 1111, false, "first"));
|
||||
add(createMessage(2L, false, "sender1", 2222, false, "second"));
|
||||
add(createMessage(3L, false, "sender2", 3333, false, "third"));
|
||||
}};
|
||||
|
||||
OutgoingMessageEntityList outgoingMessagesList = new OutgoingMessageEntityList(outgoingMessages, false);
|
||||
|
||||
when(device.getId()).thenReturn(2L);
|
||||
when(device.getSignalingKey()).thenReturn(Base64.encodeBytes(new byte[52]));
|
||||
|
||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
||||
when(account.getNumber()).thenReturn("+14152222222");
|
||||
|
||||
final Device sender1device = mock(Device.class);
|
||||
|
||||
Set<Device> sender1devices = new HashSet<Device>() {{
|
||||
add(sender1device);
|
||||
}};
|
||||
|
||||
Account sender1 = mock(Account.class);
|
||||
when(sender1.getDevices()).thenReturn(sender1devices);
|
||||
|
||||
when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1));
|
||||
when(accountsManager.get("sender2")).thenReturn(Optional.empty());
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), device.getId()))
|
||||
.thenReturn(outgoingMessagesList);
|
||||
|
||||
final List<SettableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
|
||||
final WebSocketClient client = mock(WebSocketClient.class);
|
||||
|
||||
when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any()))
|
||||
.thenAnswer(new Answer<SettableFuture<WebSocketResponseMessage>>() {
|
||||
@Override
|
||||
public SettableFuture<WebSocketResponseMessage> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
SettableFuture<WebSocketResponseMessage> future = SettableFuture.create();
|
||||
futures.add(future);
|
||||
return future;
|
||||
}
|
||||
});
|
||||
|
||||
WebsocketAddress websocketAddress = new WebsocketAddress(account.getNumber(), device.getId());
|
||||
WebSocketConnection connection = new WebSocketConnection(pushSender, receiptSender, storedMessages,
|
||||
account, device, client, "someid");
|
||||
|
||||
connection.onDispatchSubscribed(websocketAddress.serialize());
|
||||
verify(client, times(3)).sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any());
|
||||
|
||||
assertTrue(futures.size() == 3);
|
||||
|
||||
WebSocketResponseMessage response = mock(WebSocketResponseMessage.class);
|
||||
when(response.getStatus()).thenReturn(200);
|
||||
futures.get(1).set(response);
|
||||
|
||||
futures.get(0).setException(new IOException());
|
||||
futures.get(2).setException(new IOException());
|
||||
|
||||
verify(storedMessages, times(1)).delete(eq(account.getNumber()), eq(2L), eq(2L), eq(false));
|
||||
verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender1"), eq(2222L));
|
||||
|
||||
connection.onDispatchUnsubscribed(websocketAddress.serialize());
|
||||
verify(client).close(anyInt(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnlineSend() throws Exception {
|
||||
MessagesManager storedMessages = mock(MessagesManager.class);
|
||||
WebsocketSender websocketSender = mock(WebsocketSender.class);
|
||||
|
||||
when(pushSender.getWebSocketSender()).thenReturn(websocketSender);
|
||||
|
||||
Envelope firstMessage = Envelope.newBuilder()
|
||||
.setLegacyMessage(ByteString.copyFrom("first".getBytes()))
|
||||
.setSource("sender1")
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
.setSourceDevice(1)
|
||||
.setType(Envelope.Type.CIPHERTEXT)
|
||||
.build();
|
||||
|
||||
Envelope secondMessage = Envelope.newBuilder()
|
||||
.setLegacyMessage(ByteString.copyFrom("second".getBytes()))
|
||||
.setSource("sender2")
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
.setSourceDevice(2)
|
||||
.setType(Envelope.Type.CIPHERTEXT)
|
||||
.build();
|
||||
|
||||
List<OutgoingMessageEntity> pendingMessages = new LinkedList<>();
|
||||
OutgoingMessageEntityList pendingMessagesList = new OutgoingMessageEntityList(pendingMessages, false);
|
||||
|
||||
when(device.getId()).thenReturn(2L);
|
||||
when(device.getSignalingKey()).thenReturn(Base64.encodeBytes(new byte[52]));
|
||||
|
||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
||||
when(account.getNumber()).thenReturn("+14152222222");
|
||||
|
||||
final Device sender1device = mock(Device.class);
|
||||
|
||||
Set<Device> sender1devices = new HashSet<Device>() {{
|
||||
add(sender1device);
|
||||
}};
|
||||
|
||||
Account sender1 = mock(Account.class);
|
||||
when(sender1.getDevices()).thenReturn(sender1devices);
|
||||
|
||||
when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1));
|
||||
when(accountsManager.get("sender2")).thenReturn(Optional.<Account>empty());
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), device.getId()))
|
||||
.thenReturn(pendingMessagesList);
|
||||
|
||||
final List<SettableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
|
||||
final WebSocketClient client = mock(WebSocketClient.class);
|
||||
|
||||
when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any()))
|
||||
.thenAnswer(new Answer<SettableFuture<WebSocketResponseMessage>>() {
|
||||
@Override
|
||||
public SettableFuture<WebSocketResponseMessage> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
SettableFuture<WebSocketResponseMessage> future = SettableFuture.create();
|
||||
futures.add(future);
|
||||
return future;
|
||||
}
|
||||
});
|
||||
|
||||
WebsocketAddress websocketAddress = new WebsocketAddress(account.getNumber(), device.getId());
|
||||
WebSocketConnection connection = new WebSocketConnection(pushSender, receiptSender, storedMessages,
|
||||
account, device, client, "anotherid");
|
||||
|
||||
connection.onDispatchSubscribed(websocketAddress.serialize());
|
||||
connection.onDispatchMessage(websocketAddress.serialize(), PubSubProtos.PubSubMessage.newBuilder()
|
||||
.setType(PubSubProtos.PubSubMessage.Type.DELIVER)
|
||||
.setContent(ByteString.copyFrom(firstMessage.toByteArray()))
|
||||
.build().toByteArray());
|
||||
|
||||
connection.onDispatchMessage(websocketAddress.serialize(), PubSubProtos.PubSubMessage.newBuilder()
|
||||
.setType(PubSubProtos.PubSubMessage.Type.DELIVER)
|
||||
.setContent(ByteString.copyFrom(secondMessage.toByteArray()))
|
||||
.build().toByteArray());
|
||||
|
||||
verify(client, times(2)).sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any());
|
||||
|
||||
assertEquals(futures.size(), 2);
|
||||
|
||||
WebSocketResponseMessage response = mock(WebSocketResponseMessage.class);
|
||||
when(response.getStatus()).thenReturn(200);
|
||||
futures.get(1).set(response);
|
||||
futures.get(0).setException(new IOException());
|
||||
|
||||
verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp()));
|
||||
verify(websocketSender, times(1)).queueMessage(eq(account), eq(device), any(Envelope.class));
|
||||
verify(pushSender, times(1)).sendQueuedNotification(eq(account), eq(device));
|
||||
|
||||
connection.onDispatchUnsubscribed(websocketAddress.serialize());
|
||||
verify(client).close(anyInt(), anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingSend() throws Exception {
|
||||
MessagesManager storedMessages = mock(MessagesManager.class);
|
||||
WebsocketSender websocketSender = mock(WebsocketSender.class);
|
||||
|
||||
reset(websocketSender);
|
||||
reset(pushSender);
|
||||
|
||||
when(pushSender.getWebSocketSender()).thenReturn(websocketSender);
|
||||
|
||||
final Envelope firstMessage = Envelope.newBuilder()
|
||||
.setLegacyMessage(ByteString.copyFrom("first".getBytes()))
|
||||
.setSource("sender1")
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
.setSourceDevice(1)
|
||||
.setType(Envelope.Type.CIPHERTEXT)
|
||||
.build();
|
||||
|
||||
final Envelope secondMessage = Envelope.newBuilder()
|
||||
.setLegacyMessage(ByteString.copyFrom("second".getBytes()))
|
||||
.setSource("sender2")
|
||||
.setTimestamp(System.currentTimeMillis())
|
||||
.setSourceDevice(2)
|
||||
.setType(Envelope.Type.CIPHERTEXT)
|
||||
.build();
|
||||
|
||||
List<OutgoingMessageEntity> pendingMessages = new LinkedList<OutgoingMessageEntity>() {{
|
||||
add(new OutgoingMessageEntity(1, true, UUID.randomUUID(), firstMessage.getType().getNumber(), firstMessage.getRelay(),
|
||||
firstMessage.getTimestamp(), firstMessage.getSource(),
|
||||
firstMessage.getSourceDevice(), firstMessage.getLegacyMessage().toByteArray(),
|
||||
firstMessage.getContent().toByteArray(), 0));
|
||||
add(new OutgoingMessageEntity(2, false, UUID.randomUUID(), secondMessage.getType().getNumber(), secondMessage.getRelay(),
|
||||
secondMessage.getTimestamp(), secondMessage.getSource(),
|
||||
secondMessage.getSourceDevice(), secondMessage.getLegacyMessage().toByteArray(),
|
||||
secondMessage.getContent().toByteArray(), 0));
|
||||
}};
|
||||
|
||||
OutgoingMessageEntityList pendingMessagesList = new OutgoingMessageEntityList(pendingMessages, false);
|
||||
|
||||
when(device.getId()).thenReturn(2L);
|
||||
when(device.getSignalingKey()).thenReturn(Base64.encodeBytes(new byte[52]));
|
||||
|
||||
when(account.getAuthenticatedDevice()).thenReturn(Optional.of(device));
|
||||
when(account.getNumber()).thenReturn("+14152222222");
|
||||
|
||||
final Device sender1device = mock(Device.class);
|
||||
|
||||
Set<Device> sender1devices = new HashSet<Device>() {{
|
||||
add(sender1device);
|
||||
}};
|
||||
|
||||
Account sender1 = mock(Account.class);
|
||||
when(sender1.getDevices()).thenReturn(sender1devices);
|
||||
|
||||
when(accountsManager.get("sender1")).thenReturn(Optional.of(sender1));
|
||||
when(accountsManager.get("sender2")).thenReturn(Optional.<Account>empty());
|
||||
|
||||
when(storedMessages.getMessagesForDevice(account.getNumber(), device.getId()))
|
||||
.thenReturn(pendingMessagesList);
|
||||
|
||||
final List<SettableFuture<WebSocketResponseMessage>> futures = new LinkedList<>();
|
||||
final WebSocketClient client = mock(WebSocketClient.class);
|
||||
|
||||
when(client.sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any()))
|
||||
.thenAnswer(new Answer<SettableFuture<WebSocketResponseMessage>>() {
|
||||
@Override
|
||||
public SettableFuture<WebSocketResponseMessage> answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
SettableFuture<WebSocketResponseMessage> future = SettableFuture.create();
|
||||
futures.add(future);
|
||||
return future;
|
||||
}
|
||||
});
|
||||
|
||||
WebsocketAddress websocketAddress = new WebsocketAddress(account.getNumber(), device.getId());
|
||||
WebSocketConnection connection = new WebSocketConnection(pushSender, receiptSender, storedMessages,
|
||||
account, device, client, "onemoreid");
|
||||
|
||||
connection.onDispatchSubscribed(websocketAddress.serialize());
|
||||
|
||||
verify(client, times(2)).sendRequest(eq("PUT"), eq("/api/v1/message"), ArgumentMatchers.nullable(List.class), ArgumentMatchers.<Optional<byte[]>>any());
|
||||
|
||||
assertEquals(futures.size(), 2);
|
||||
|
||||
WebSocketResponseMessage response = mock(WebSocketResponseMessage.class);
|
||||
when(response.getStatus()).thenReturn(200);
|
||||
futures.get(1).set(response);
|
||||
futures.get(0).setException(new IOException());
|
||||
|
||||
verify(receiptSender, times(1)).sendReceipt(eq(account), eq("sender2"), eq(secondMessage.getTimestamp()));
|
||||
verifyNoMoreInteractions(websocketSender);
|
||||
verifyNoMoreInteractions(pushSender);
|
||||
|
||||
connection.onDispatchUnsubscribed(websocketAddress.serialize());
|
||||
verify(client).close(anyInt(), anyString());
|
||||
}
|
||||
|
||||
|
||||
private OutgoingMessageEntity createMessage(long id, boolean cached, String sender, long timestamp, boolean receipt, String content) {
|
||||
return new OutgoingMessageEntity(id, cached, UUID.randomUUID(), receipt ? Envelope.Type.RECEIPT_VALUE : Envelope.Type.CIPHERTEXT_VALUE,
|
||||
null, timestamp, sender, 1, content.getBytes(), null, 0);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user