package org.whispersystems.textsecuregcm.tests.controllers;
import com.google.common.base.Optional;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;
import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.whispersystems.dropwizard.simpleauth.AuthValueFactoryProvider;
import org.whispersystems.textsecuregcm.auth.StoredVerificationCode;
import org.whispersystems.textsecuregcm.auth.TurnTokenGenerator;
import org.whispersystems.textsecuregcm.controllers.AccountController;
import org.whispersystems.textsecuregcm.entities.AccountAttributes;
import org.whispersystems.textsecuregcm.limits.RateLimiter;
import org.whispersystems.textsecuregcm.limits.RateLimiters;
import org.whispersystems.textsecuregcm.providers.TimeProvider;
import org.whispersystems.textsecuregcm.sms.SmsSender;
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.util.HashMap;
import java.util.concurrent.TimeUnit;
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 PendingAccountsManager pendingAccountsManager = mock(PendingAccountsManager.class);
private AccountsManager accountsManager = mock(AccountsManager.class );
private RateLimiters rateLimiters = mock(RateLimiters.class );
private RateLimiter rateLimiter = mock(RateLimiter.class );
private SmsSender smsSender = mock(SmsSender.class );
private MessagesManager storedMessages = mock(MessagesManager.class );
private TimeProvider timeProvider = mock(TimeProvider.class );
private TurnTokenGenerator turnTokenGenerator = mock(TurnTokenGenerator.class);
private static byte[] authorizationKey = decodeHex("3a078586eea8971155f5c1ebd73c8c923cbec1c3ed22a54722e4e88321dc749f");
@Rule
public final ResourceTestRule resources = ResourceTestRule.builder()
.addProvider(AuthHelper.getAuthFilter())
.addProvider(new AuthValueFactoryProvider.Binder())
.setMapper(SystemMapper.getMapper())
.setTestContainerFactory(new GrizzlyWebTestContainerFactory())
.addResource(new AccountController(pendingAccountsManager,
accountsManager,
rateLimiters,
smsSender,
storedMessages,
timeProvider,
Optional.of(authorizationKey),
turnTokenGenerator,
new HashMap<String, Integer>()))
.build();
@Before
public void setup() throws Exception {
when(rateLimiters.getSmsDestinationLimiter()).thenReturn(rateLimiter);
when(rateLimiters.getVoiceDestinationLimiter()).thenReturn(rateLimiter);
when(rateLimiters.getVerifyLimiter()).thenReturn(rateLimiter);
when(timeProvider.getCurrentTimeMillis()).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))));
}
@Test
public void testSendCode() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.request()
.get();
assertThat(response.getStatus()).isEqualTo(200);
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.<String>absent()), anyString());
}
@Test
public void testSendiOSCode() throws Exception {
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/sms/code/%s", SENDER))
.queryParam("client", "ios")
.request()
.get();
assertThat(response.getStatus()).isEqualTo(200);
verify(smsSender).deliverSmsVerification(eq(SENDER), eq(Optional.of("ios")), 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),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(204);
verify(accountsManager, times(1)).create(isA(Account.class));
}
@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),
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),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(accountsManager);
}
@Test
public void testVerifyToken() throws Exception {
when(timeProvider.getCurrentTimeMillis()).thenReturn(1415917053106L);
String token = SENDER + ":1415906573:af4f046107c21721224a";
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/token/%s", token))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 4444),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(204);
verify(accountsManager, times(1)).create(isA(Account.class));
}
@Test
public void testVerifyBadToken() throws Exception {
when(timeProvider.getCurrentTimeMillis()).thenReturn(1415917053106L);
String token = SENDER + ":1415906574:af4f046107c21721224a";
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/token/%s", token))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 4444),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(accountsManager);
}
@Test
public void testVerifyWrongToken() throws Exception {
when(timeProvider.getCurrentTimeMillis()).thenReturn(1415917053106L);
String token = SENDER + ":1415906573:af4f046107c21721224a";
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/token/%s", token))
.request()
.header("Authorization", AuthHelper.getAuthHeader("+14151111111", "bar"))
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 4444),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(accountsManager);
}
@Test
public void testVerifyExpiredToken() throws Exception {
when(timeProvider.getCurrentTimeMillis()).thenReturn(1416003757901L);
String token = SENDER + ":1415906573:af4f046107c21721224a";
Response response =
resources.getJerseyTest()
.target(String.format("/v1/accounts/token/%s", token))
.request()
.header("Authorization", AuthHelper.getAuthHeader(SENDER, "bar"))
.put(Entity.entity(new AccountAttributes("keykeykeykey", false, 4444),
MediaType.APPLICATION_JSON_TYPE));
assertThat(response.getStatus()).isEqualTo(403);
verifyNoMoreInteractions(accountsManager);
}
private static byte[] decodeHex(String hex) {
try {
return Hex.decodeHex(hex.toCharArray());
} catch (DecoderException e) {
throw new AssertionError(e);
}
}
}