Request a push challenge and supply to SMS and voice verification.
This commit is contained in:
parent
d72d4c4c41
commit
5b61c8ac18
4 changed files with 281 additions and 10 deletions
|
@ -9,9 +9,6 @@ import android.content.Intent;
|
|||
import android.content.IntentFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.TextUtils;
|
||||
import android.text.TextWatcher;
|
||||
|
@ -28,6 +25,10 @@ import android.widget.Spinner;
|
|||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
import com.google.android.gms.auth.api.phone.SmsRetriever;
|
||||
import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
|
||||
|
@ -71,6 +72,7 @@ import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
|||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.push.AccountManagerFactory;
|
||||
import org.thoughtcrime.securesms.registration.CaptchaActivity;
|
||||
import org.thoughtcrime.securesms.registration.PushChallengeRequest;
|
||||
import org.thoughtcrime.securesms.service.DirectoryRefreshListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.service.VerificationCodeParser;
|
||||
|
@ -115,7 +117,9 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
|
|||
private static final int SCENE_TRANSITION_DURATION = 250;
|
||||
private static final int DEBUG_TAP_TARGET = 8;
|
||||
private static final int DEBUG_TAP_ANNOUNCE = 4;
|
||||
public static final String RE_REGISTRATION_EXTRA = "re_registration";
|
||||
private static final long PUSH_REQUEST_TIMEOUT_MS = 5000L;
|
||||
|
||||
public static final String RE_REGISTRATION_EXTRA = "re_registration";
|
||||
|
||||
private static final String TAG = RegistrationActivity.class.getSimpleName();
|
||||
|
||||
|
@ -492,7 +496,10 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
|
|||
}
|
||||
|
||||
accountManager = AccountManagerFactory.createManager(RegistrationActivity.this, e164number, password);
|
||||
accountManager.requestSmsVerificationCode(smsRetrieverSupported, registrationState.captchaToken, Optional.absent());
|
||||
|
||||
Optional<String> pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, fcmToken, e164number, PUSH_REQUEST_TIMEOUT_MS);
|
||||
|
||||
accountManager.requestSmsVerificationCode(smsRetrieverSupported, registrationState.captchaToken, pushChallenge);
|
||||
|
||||
return new VerificationRequestResult(password, fcmToken, Optional.absent());
|
||||
} catch (IOException e) {
|
||||
|
@ -668,6 +675,8 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
|
|||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private void handlePhoneCallRequest() {
|
||||
final String e164number = getConfiguredE164Number();
|
||||
|
||||
if (registrationState.state == RegistrationState.State.VERIFYING) {
|
||||
callMeCountDownView.startCountDown(300);
|
||||
|
||||
|
@ -675,7 +684,9 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
|
|||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
try {
|
||||
accountManager.requestVoiceVerificationCode(Locale.getDefault(), registrationState.captchaToken, Optional.absent());
|
||||
Optional<String> pushChallenge = PushChallengeRequest.getPushChallengeBlocking(accountManager, getFcmToken(), e164number, PUSH_REQUEST_TIMEOUT_MS);
|
||||
|
||||
accountManager.requestVoiceVerificationCode(Locale.getDefault(), registrationState.captchaToken, pushChallenge);
|
||||
} catch (CaptchaRequiredException e) {
|
||||
requestCaptcha(false);
|
||||
} catch (IOException e) {
|
||||
|
@ -688,6 +699,15 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif
|
|||
}
|
||||
}
|
||||
|
||||
private Optional<String> getFcmToken() {
|
||||
final boolean gcmSupported = PlayServicesUtil.getPlayServicesStatus(this) == PlayServicesStatus.SUCCESS;
|
||||
if (gcmSupported) {
|
||||
return FcmUtil.getToken();
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyAccount(@NonNull String code, @Nullable String pin) throws IOException {
|
||||
int registrationId = KeyHelper.generateRegistrationId(false);
|
||||
byte[] unidentifiedAccessKey = UnidentifiedAccessUtil.getSelfUnidentifiedAccessKey(RegistrationActivity.this);
|
||||
|
|
|
@ -4,6 +4,8 @@ import android.content.Context;
|
|||
import android.os.Build;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.google.firebase.messaging.FirebaseMessagingService;
|
||||
import com.google.firebase.messaging.RemoteMessage;
|
||||
|
||||
|
@ -13,6 +15,7 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
|||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.registration.PushChallengeRequest;
|
||||
import org.thoughtcrime.securesms.util.PowerManagerCompat;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
@ -38,11 +41,17 @@ public class FcmService extends FirebaseMessagingService implements InjectableTy
|
|||
@Override
|
||||
public void onMessageReceived(RemoteMessage remoteMessage) {
|
||||
Log.i(TAG, "FCM message... Original Priority: " + remoteMessage.getOriginalPriority() + ", Actual Priority: " + remoteMessage.getPriority());
|
||||
ApplicationContext.getInstance(getApplicationContext()).injectDependencies(this);
|
||||
|
||||
WakeLockUtil.runWithLock(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK, 60000, WAKE_LOCK_TAG, () -> {
|
||||
handleReceivedNotification(getApplicationContext());
|
||||
});
|
||||
String challenge = remoteMessage.getData().get("challenge");
|
||||
if (challenge != null) {
|
||||
handlePushChallenge(challenge);
|
||||
} else {
|
||||
ApplicationContext.getInstance(getApplicationContext()).injectDependencies(this);
|
||||
|
||||
WakeLockUtil.runWithLock(getApplicationContext(), PowerManager.PARTIAL_WAKE_LOCK, 60000, WAKE_LOCK_TAG, () ->
|
||||
handleReceivedNotification(getApplicationContext())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,6 +104,12 @@ public class FcmService extends FirebaseMessagingService implements InjectableTy
|
|||
Log.i(TAG, "Processing complete.");
|
||||
}
|
||||
|
||||
private static void handlePushChallenge(@NonNull String challenge) {
|
||||
Log.d(TAG, String.format("Got a push challenge \"%s\"", challenge));
|
||||
|
||||
PushChallengeRequest.postChallengeResponse(challenge);
|
||||
}
|
||||
|
||||
private static synchronized boolean incrementActiveGcmCount() {
|
||||
if (activeCount < 2) {
|
||||
activeCount++;
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class PushChallengeRequest {
|
||||
|
||||
private static final String TAG = Log.tag(PushChallengeRequest.class);
|
||||
|
||||
/**
|
||||
* Requests a push challenge and waits for the response.
|
||||
* <p>
|
||||
* Blocks the current thread for up to {@param timeoutMs} milliseconds.
|
||||
*
|
||||
* @param accountManager Account manager to request the push from.
|
||||
* @param fcmToken Optional FCM token. If not present will return absent.
|
||||
* @param e164number Local number.
|
||||
* @param timeoutMs Timeout in milliseconds
|
||||
* @return Either returns a challenge, or absent.
|
||||
*/
|
||||
@WorkerThread
|
||||
public static Optional<String> getPushChallengeBlocking(@NonNull SignalServiceAccountManager accountManager,
|
||||
@NonNull Optional<String> fcmToken,
|
||||
@NonNull String e164number,
|
||||
long timeoutMs)
|
||||
{
|
||||
if (!fcmToken.isPresent()) {
|
||||
Log.w(TAG, "Push challenge not requested, as no FCM token was present");
|
||||
return Optional.absent();
|
||||
}
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
Log.i(TAG, "Requesting a push challenge");
|
||||
|
||||
Request request = new Request(accountManager, fcmToken.get(), e164number, timeoutMs);
|
||||
|
||||
Optional<String> challenge = request.requestAndReceiveChallengeBlocking();
|
||||
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
|
||||
if (challenge.isPresent()) {
|
||||
Log.i(TAG, String.format(Locale.US, "Received a push challenge \"%s\" in %d ms", challenge.get(), duration));
|
||||
} else {
|
||||
Log.w(TAG, String.format(Locale.US, "Did not received a push challenge in %d ms", duration));
|
||||
}
|
||||
return challenge;
|
||||
}
|
||||
|
||||
public static void postChallengeResponse(@NonNull String challenge) {
|
||||
EventBus.getDefault().post(new PushChallengeEvent(challenge));
|
||||
}
|
||||
|
||||
private static class Request {
|
||||
|
||||
private final CountDownLatch latch;
|
||||
private final AtomicReference<String> challenge;
|
||||
private final SignalServiceAccountManager accountManager;
|
||||
private final String fcmToken;
|
||||
private final String e164number;
|
||||
private final long timeoutMs;
|
||||
|
||||
private Request(@NonNull SignalServiceAccountManager accountManager,
|
||||
@NonNull String fcmToken,
|
||||
@NonNull String e164number,
|
||||
long timeoutMs)
|
||||
{
|
||||
this.latch = new CountDownLatch(1);
|
||||
this.challenge = new AtomicReference<>();
|
||||
this.accountManager = accountManager;
|
||||
this.fcmToken = fcmToken;
|
||||
this.e164number = e164number;
|
||||
this.timeoutMs = timeoutMs;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private Optional<String> requestAndReceiveChallengeBlocking() {
|
||||
EventBus eventBus = EventBus.getDefault();
|
||||
|
||||
eventBus.register(this);
|
||||
try {
|
||||
accountManager.requestPushChallenge(fcmToken, e164number);
|
||||
|
||||
latch.await(timeoutMs, TimeUnit.MILLISECONDS);
|
||||
|
||||
return Optional.fromNullable(challenge.get());
|
||||
} catch (InterruptedException | IOException e) {
|
||||
Log.w(TAG, "Error getting push challenge", e);
|
||||
return Optional.absent();
|
||||
} finally {
|
||||
eventBus.unregister(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.POSTING)
|
||||
public void onChallengeEvent(@NonNull PushChallengeEvent pushChallengeEvent) {
|
||||
challenge.set(pushChallengeEvent.challenge);
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
static class PushChallengeEvent {
|
||||
private final String challenge;
|
||||
|
||||
PushChallengeEvent(String challenge) {
|
||||
this.challenge = challenge;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package org.thoughtcrime.securesms.registration;
|
||||
|
||||
import android.app.Application;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
|
||||
import static org.hamcrest.Matchers.lessThan;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, application = Application.class)
|
||||
public final class PushChallengeRequestTest {
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_returns_absent_if_times_out() {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
|
||||
Optional<String> challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 50L);
|
||||
|
||||
assertFalse(challenge.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_waits_for_specified_period() {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 250L);
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
|
||||
assertThat(duration, greaterThanOrEqualTo(250L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_completes_fast_if_posted_to_event_bus() throws IOException {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
doAnswer(invocation -> {
|
||||
AsyncTask.execute(() -> PushChallengeRequest.postChallengeResponse("CHALLENGE"));
|
||||
return null;
|
||||
}).when(signal).requestPushChallenge("token", "+123456");
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
Optional<String> challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 500L);
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
|
||||
assertThat(duration, lessThan(500L));
|
||||
verify(signal).requestPushChallenge("token", "+123456");
|
||||
verifyNoMoreInteractions(signal);
|
||||
|
||||
assertTrue(challenge.isPresent());
|
||||
assertEquals("CHALLENGE", challenge.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_returns_fast_if_no_fcm_token_supplied() {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
PushChallengeRequest.getPushChallengeBlocking(signal, Optional.absent(), "+123456", 500L);
|
||||
long duration = System.currentTimeMillis() - startTime;
|
||||
|
||||
assertThat(duration, lessThan(500L));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_returns_absent_if_no_fcm_token_supplied() {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
|
||||
Optional<String> challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.absent(), "+123456", 500L);
|
||||
|
||||
verifyZeroInteractions(signal);
|
||||
assertFalse(challenge.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_returns_absent_if_any_IOException_is_thrown() throws IOException {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
|
||||
doThrow(new IOException()).when(signal).requestPushChallenge(any(), any());
|
||||
|
||||
Optional<String> challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 500L);
|
||||
|
||||
assertFalse(challenge.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getPushChallengeBlocking_returns_absent_if_any_RuntimeException_is_thrown() throws IOException {
|
||||
SignalServiceAccountManager signal = mock(SignalServiceAccountManager.class);
|
||||
|
||||
doThrow(new RuntimeException()).when(signal).requestPushChallenge(any(), any());
|
||||
|
||||
Optional<String> challenge = PushChallengeRequest.getPushChallengeBlocking(signal, Optional.of("token"), "+123456", 500L);
|
||||
|
||||
assertFalse(challenge.isPresent());
|
||||
}
|
||||
}
|
Loading…
Add table
Reference in a new issue