Support requesting a CAPTCHA during registration.

This commit is contained in:
Greyson Parrelli 2019-02-13 11:52:55 -08:00
parent 2cfa431cad
commit 02b0800b22
5 changed files with 187 additions and 29 deletions

View File

@ -305,6 +305,12 @@
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".registration.CaptchaActivity"
android:launchMode="singleTask"
android:theme="@style/TextSecure.LightNoActionBar"
android:windowSoftInputMode="stateUnchanged"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceActivity"
android:label="@string/AndroidManifest__linked_devices"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/registration_captcha_title"
style="@style/Signal.Text.Headline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginLeft="24dp"
android:layout_marginTop="24dp"
android:layout_marginEnd="24dp"
android:layout_marginRight="24dp"
android:text="@string/RegistrationActivity_we_need_to_verify_that_youre_human"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<WebView
android:id="@+id/registration_captcha_web_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/registration_captcha_title" />
</android.support.constraint.ConstraintLayout>

View File

@ -597,6 +597,8 @@
<item quantity="one">You are now %d step away from submitting a debug log.</item>
<item quantity="other">You are now %d steps away from submitting a debug log.</item>
</plurals>
<string name="RegistrationActivity_we_need_to_verify_that_youre_human">We need to verify that you\'re human.</string>
<string name="RegistrationActivity_failed_to_verify_the_captcha">Failed to verify the CAPTCHA</string>
<!-- ScribbleActivity -->
<string name="ScribbleActivity_save_failure">Failed to save image changes</string>

View File

@ -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<Void, Void, BackupImportResult>() {
@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<Void, Void, Pair<String, Optional<String>>> () {
new AsyncTask<Void, Void, VerificationRequestResult> () {
@Override
protected @Nullable Pair<String, Optional<String>> 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<String, Optional<String>> 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<Integer> 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<String> fcmToken;
private final Optional<IOException> exception;
private VerificationRequestResult(String password, Optional<String> fcmToken, Optional<IOException> 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<String> gcmToken;
private final Optional<String> captchaToken;
RegistrationState(State state, String e164number, String password, Optional<String> gcmToken) {
this.state = state;
this.e164number = e164number;
this.password = password;
this.gcmToken = gcmToken;
RegistrationState(State state, String e164number, String password, Optional<String> gcmToken, Optional<String> 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<String> captchaToken, RegistrationState previous) {
this.state = previous.state;
this.e164number = previous.e164number;
this.password = previous.password;
this.gcmToken = previous.gcmToken;
this.captchaToken = captchaToken;
}
}

View File

@ -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();
}
}