From 02b0800b22e2faaa1f659d34ac3bb2f44eda3631 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 13 Feb 2019 11:52:55 -0800 Subject: [PATCH] Support requesting a CAPTCHA during registration. --- AndroidManifest.xml | 6 ++ res/layout/captcha_activity.xml | 41 +++++++ res/values/strings.xml | 2 + .../securesms/RegistrationActivity.java | 102 +++++++++++++----- .../registration/CaptchaActivity.java | 65 +++++++++++ 5 files changed, 187 insertions(+), 29 deletions(-) create mode 100644 res/layout/captcha_activity.xml create mode 100644 src/org/thoughtcrime/securesms/registration/CaptchaActivity.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index a47bc73675..e83215b3ef 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -305,6 +305,12 @@ android:windowSoftInputMode="stateUnchanged" android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> + + diff --git a/res/layout/captcha_activity.xml b/res/layout/captcha_activity.xml new file mode 100644 index 0000000000..9528904b6a --- /dev/null +++ b/res/layout/captcha_activity.xml @@ -0,0 +1,41 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index d954992de7..573caa1759 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -597,6 +597,8 @@ You are now %d step away from submitting a debug log. You are now %d steps away from submitting a debug log. + We need to verify that you\'re human. + Failed to verify the CAPTCHA Failed to save image changes diff --git a/src/org/thoughtcrime/securesms/RegistrationActivity.java b/src/org/thoughtcrime/securesms/RegistrationActivity.java index d951fb8b64..2eaec73743 100644 --- a/src/org/thoughtcrime/securesms/RegistrationActivity.java +++ b/src/org/thoughtcrime/securesms/RegistrationActivity.java @@ -78,6 +78,7 @@ import org.thoughtcrime.securesms.logging.Log; 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.service.DirectoryRefreshListener; import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener; import org.thoughtcrime.securesms.service.VerificationCodeParser; @@ -96,6 +97,7 @@ import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceAccountManager; +import org.whispersystems.signalservice.api.push.exceptions.CaptchaRequiredException; import org.whispersystems.signalservice.api.push.exceptions.RateLimitException; import org.whispersystems.signalservice.api.util.PhoneNumberFormatter; import org.whispersystems.signalservice.internal.push.LockedException; @@ -117,6 +119,7 @@ import java.util.concurrent.TimeUnit; public class RegistrationActivity extends BaseActionBarActivity implements VerificationCodeView.OnCodeEnteredListener { private static final int PICK_COUNTRY = 1; + private static final int CAPTCHA = 24601; private static final int SCENE_TRANSITION_DURATION = 250; private static final int DEBUG_TAP_TARGET = 8; private static final int DEBUG_TAP_ANNOUNCE = 4; @@ -185,6 +188,16 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif this.countryCode.setText(String.valueOf(data.getIntExtra("country_code", 1))); setCountryDisplay(data.getStringExtra("country_name")); setCountryFormatter(data.getIntExtra("country_code", 1)); + } else if (requestCode == CAPTCHA && resultCode == RESULT_OK && data != null) { + registrationState = new RegistrationState(Optional.fromNullable(data.getStringExtra(CaptchaActivity.KEY_TOKEN)), registrationState); + + if (data.getBooleanExtra(CaptchaActivity.KEY_IS_SMS, true)) { + handleRegister(); + } else { + handlePhoneCallRequest(); + } + } else if (requestCode == CAPTCHA) { + Toast.makeText(this, R.string.RegistrationActivity_failed_to_verify_the_captcha, Toast.LENGTH_LONG).show(); } } @@ -226,7 +239,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif this.pinForgotButton = findViewById(R.id.forgot_button); this.pinClarificationContainer = findViewById(R.id.pin_clarification_container); - this.registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, null); + this.registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, Optional.absent(), Optional.absent()); this.countryCode.addTextChangedListener(new CountryCodeChangedListener()); this.number.addTextChangedListener(new NumberChangedListener()); @@ -388,13 +401,14 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif restoreButton.setIndeterminateProgressMode(true); restoreButton.setProgress(50); + final String passphrase = prompt.getText().toString(); + new AsyncTask() { @Override protected BackupImportResult doInBackground(Void... voids) { try { - Context context = RegistrationActivity.this; - String passphrase = prompt.getText().toString(); - SQLiteDatabase database = DatabaseFactory.getBackupDatabase(context); + Context context = RegistrationActivity.this; + SQLiteDatabase database = DatabaseFactory.getBackupDatabase(context); FullBackupImporter.importFile(context, AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), @@ -498,9 +512,9 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif @SuppressLint("StaticFieldLeak") private void requestVerificationCode(@NonNull String e164number, boolean gcmSupported, boolean smsRetrieverSupported) { - new AsyncTask>> () { + new AsyncTask () { @Override - protected @Nullable Pair> doInBackground(Void... voids) { + protected @NonNull VerificationRequestResult doInBackground(Void... voids) { try { markAsVerifying(true); @@ -515,29 +529,34 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif } accountManager = AccountManagerFactory.createManager(RegistrationActivity.this, e164number, password); - accountManager.requestSmsVerificationCode(smsRetrieverSupported); + accountManager.requestSmsVerificationCode(smsRetrieverSupported, registrationState.captchaToken); - return new Pair<>(password, fcmToken); + return new VerificationRequestResult(password, fcmToken, Optional.absent()); } catch (IOException e) { Log.w(TAG, "Error during account registration", e); - return null; + return new VerificationRequestResult(null, Optional.absent(), Optional.of(e)); } } - protected void onPostExecute(@Nullable Pair> result) { - if (result == null) { + protected void onPostExecute(@NonNull VerificationRequestResult result) { + if (result.exception.isPresent() && result.exception.get() instanceof CaptchaRequiredException) { + requestCaptcha(true); + } else if (result.exception.isPresent()) { Toast.makeText(RegistrationActivity.this, R.string.RegistrationActivity_unable_to_connect_to_service, Toast.LENGTH_LONG).show(); createButton.setIndeterminateProgressMode(false); createButton.setProgress(0); - return; + } else { + registrationState = new RegistrationState(RegistrationState.State.VERIFYING, e164number, result.password, result.fcmToken, Optional.absent()); + displayVerificationView(e164number, 64); } - - registrationState = new RegistrationState(RegistrationState.State.VERIFYING, e164number, result.first, result.second); - displayVerificationView(e164number, 64); } }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } + private void requestCaptcha(boolean isSms) { + startActivityForResult(CaptchaActivity.getIntent(this, isSms), CAPTCHA); + } + private void handleVerificationCodeReceived(@Nullable String code) { List parsedCode = convertVerificationCodeToDigits(code); @@ -693,7 +712,9 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif @Override protected Void doInBackground(Void... voids) { try { - accountManager.requestVoiceVerificationCode(Locale.getDefault()); + accountManager.requestVoiceVerificationCode(Locale.getDefault(), registrationState.captchaToken); + } catch (CaptchaRequiredException e) { + requestCaptcha(false); } catch (IOException e) { Log.w(TAG, e); } @@ -892,7 +913,7 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif @Override public void onClick(View widget) { displayInitialView(false); - registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, null); + registrationState = new RegistrationState(RegistrationState.State.INITIAL, null, null, Optional.absent(), Optional.absent()); } @Override @@ -1157,28 +1178,51 @@ public class RegistrationActivity extends BaseActionBarActivity implements Verif } } + private static class VerificationRequestResult { + private final String password; + private final Optional fcmToken; + private final Optional exception; + + private VerificationRequestResult(String password, Optional fcmToken, Optional exception) { + this.password = password; + this.fcmToken = fcmToken; + this.exception = exception; + } + } + private static class RegistrationState { private enum State { INITIAL, VERIFYING, CHECKING, PIN } - private final State state; - private final String e164number; - private final String password; + private final State state; + private final String e164number; + private final String password; private final Optional gcmToken; + private final Optional captchaToken; - RegistrationState(State state, String e164number, String password, Optional gcmToken) { - this.state = state; - this.e164number = e164number; - this.password = password; - this.gcmToken = gcmToken; + RegistrationState(State state, String e164number, String password, Optional gcmToken, Optional captchaToken) { + this.state = state; + this.e164number = e164number; + this.password = password; + this.gcmToken = gcmToken; + this.captchaToken = captchaToken; } RegistrationState(State state, RegistrationState previous) { - this.state = state; - this.e164number = previous.e164number; - this.password = previous.password; - this.gcmToken = previous.gcmToken; + this.state = state; + this.e164number = previous.e164number; + this.password = previous.password; + this.gcmToken = previous.gcmToken; + this.captchaToken = previous.captchaToken; + } + + RegistrationState(Optional captchaToken, RegistrationState previous) { + this.state = previous.state; + this.e164number = previous.e164number; + this.password = previous.password; + this.gcmToken = previous.gcmToken; + this.captchaToken = captchaToken; } } diff --git a/src/org/thoughtcrime/securesms/registration/CaptchaActivity.java b/src/org/thoughtcrime/securesms/registration/CaptchaActivity.java new file mode 100644 index 0000000000..ebff3ff730 --- /dev/null +++ b/src/org/thoughtcrime/securesms/registration/CaptchaActivity.java @@ -0,0 +1,65 @@ +package org.thoughtcrime.securesms.registration; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.text.TextUtils; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import org.thoughtcrime.securesms.BaseActionBarActivity; +import org.thoughtcrime.securesms.R; + +public class CaptchaActivity extends BaseActionBarActivity { + + public static final String KEY_TOKEN = "token"; + public static final String KEY_IS_SMS = "is_sms"; + + private static final String SIGNAL_SCHEME = "signalcaptcha://"; + + public static Intent getIntent(@NonNull Context context, boolean isSms) { + Intent intent = new Intent(context, CaptchaActivity.class); + intent.putExtra(KEY_IS_SMS, isSms); + return intent; + } + + @SuppressLint("SetJavaScriptEnabled") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.captcha_activity); + + WebView webView = findViewById(R.id.registration_captcha_web_view); + + webView.getSettings().setJavaScriptEnabled(true); + webView.clearCache(true); + + webView.setWebViewClient(new WebViewClient() { + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + if (url != null && url.startsWith(SIGNAL_SCHEME)) { + handleToken(url.substring(SIGNAL_SCHEME.length())); + return true; + } + return false; + } + }); + + webView.loadUrl("https://signalcaptchas.org/registration/generate.html\n"); + } + + public void handleToken(String token) { + if (!TextUtils.isEmpty(token)) { + Intent result = new Intent(); + result.putExtra(KEY_TOKEN, token); + result.putExtra(KEY_IS_SMS, getIntent().getBooleanExtra(KEY_IS_SMS, true)); + setResult(RESULT_OK, result); + } else { + setResult(RESULT_CANCELED); + } + + finish(); + } +}