Add the ability to opt out of PINs.

fork-5.53.8
Greyson Parrelli 2020-07-09 16:04:30 -07:00
rodzic c26dcc2618
commit 04a8996348
24 zmienionych plików z 550 dodań i 101 usunięć

Wyświetl plik

@ -100,6 +100,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.insights.InsightsLauncher;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mediasend.MediaSendActivity;

Wyświetl plik

@ -70,7 +70,7 @@ public class RefreshAttributesJob extends BaseJob {
registrationLockV1 = TextSecurePreferences.getDeprecatedV1RegistrationLockPin(context);
}
SignalServiceProfile.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin());
SignalServiceProfile.Capabilities capabilities = AppCapabilities.getCapabilities(kbsValues.hasPin() && !kbsValues.hasOptedOut());
Log.i(TAG, "Calling setAccountAttributes() reglockV1? " + !TextUtils.isEmpty(registrationLockV1) + ", reglockV2? " + !TextUtils.isEmpty(registrationLockV2) + ", pin? " + kbsValues.hasPin() +
"\n Capabilities:" +
"\n Storage? " + capabilities.isStorage() +

Wyświetl plik

@ -20,6 +20,7 @@ public final class KbsValues extends SignalStoreValues {
private static final String PIN = "kbs.pin";
private static final String LOCK_LOCAL_PIN_HASH = "kbs.registration_lock_local_pin_hash";
private static final String LAST_CREATE_FAILED_TIMESTAMP = "kbs.last_create_failed_timestamp";
public static final String OPTED_OUT = "kbs.opted_out";
KbsValues(KeyValueStore store) {
super(store);
@ -41,6 +42,7 @@ public final class KbsValues extends SignalStoreValues {
.remove(LOCK_LOCAL_PIN_HASH)
.remove(PIN)
.remove(LAST_CREATE_FAILED_TIMESTAMP)
.remove(OPTED_OUT)
.commit();
}
@ -142,6 +144,33 @@ public final class KbsValues extends SignalStoreValues {
return getLocalPinHash() != null;
}
/**
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}.
*/
public synchronized void optIn() {
putBoolean(OPTED_OUT, false);
}
/**
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}.
*/
public synchronized void optOut() {
putBoolean(OPTED_OUT, true);
}
/**
* Should only be called by {@link org.thoughtcrime.securesms.pin.PinState}.
*/
public synchronized void resetMasterKey() {
getStore().beginWrite()
.remove(MASTER_KEY)
.apply();
}
public synchronized boolean hasOptedOut() {
return getBoolean(OPTED_OUT, false);
}
public synchronized @Nullable TokenResponse getRegistrationLockTokenResponse() {
String token = getStore().getString(TOKEN_RESPONSE, null);

Wyświetl plik

@ -1,9 +1,13 @@
package org.thoughtcrime.securesms.lock.v2;
import android.content.Intent;
import android.os.Bundle;
import android.text.InputType;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
@ -13,11 +17,16 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import com.airbnb.lottie.LottieAnimationView;
import org.thoughtcrime.securesms.LoggingFragment;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
import org.thoughtcrime.securesms.pin.PinState;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.text.AfterTextChanged;
import org.thoughtcrime.securesms.util.views.LearnMoreTextView;
@ -34,6 +43,12 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
private LottieAnimationView lottieEnd;
private ViewModel viewModel;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@ -63,6 +78,10 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.BaseKbsPinFragment__learn_more_url));
});
Toolbar toolbar = view.findViewById(R.id.kbs_pin_toolbar);
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(null);
initializeListeners();
}
@ -73,6 +92,34 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
input.requestFocus();
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.pin_skip, menu);
}
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
if (RegistrationLockUtil.userHasRegistrationLock(requireContext()) ||
SignalStore.kbsValues().hasPin())
{
menu.clear();
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_pin_learn_more:
onLearnMore();
return true;
case R.id.menu_pin_skip:
onPinSkipped();
return true;
}
return false;
}
protected abstract ViewModel initializeViewModel();
protected abstract void initializeViewStates();
@ -109,6 +156,15 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
return confirm;
}
protected void closeNavGraphBranch() {
Intent activityIntent = requireActivity().getIntent();
if (activityIntent != null && activityIntent.hasExtra("next_intent")) {
startActivity(activityIntent.getParcelableExtra("next_intent"));
}
requireActivity().finish();
}
private void initializeViews(@NonNull View view) {
title = view.findViewById(R.id.edit_kbs_pin_title);
description = view.findViewById(R.id.edit_kbs_pin_description);
@ -152,4 +208,17 @@ abstract class BaseKbsPinFragment<ViewModel extends BaseKbsPinViewModel> extends
return R.string.BaseKbsPinFragment__create_alphanumeric_pin;
}
}
private void onLearnMore() {
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.KbsSplashFragment__learn_more_link));
}
private void onPinSkipped() {
PinOptOutDialog.showForSkip(requireContext(),
this::closeNavGraphBranch,
() -> {
PinState.onPinCreateFailure();
closeNavGraphBranch();
});
}
}

Wyświetl plik

@ -186,15 +186,6 @@ public class ConfirmKbsPinFragment extends BaseKbsPinFragment<ConfirmKbsPinViewM
.show();
}
private void closeNavGraphBranch() {
Intent activityIntent = requireActivity().getIntent();
if (activityIntent != null && activityIntent.hasExtra("next_intent")) {
startActivity(activityIntent.getParcelableExtra("next_intent"));
}
requireActivity().finish();
}
private void markMegaphoneSeenIfNecessary() {
ApplicationDependencies.getMegaphoneRepository().markSeen(Megaphones.Event.PINS_FOR_ALL);
}

Wyświetl plik

@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.lock.v2;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.PluralsRes;

Wyświetl plik

@ -1,10 +1,11 @@
package org.thoughtcrime.securesms.lock.v2;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@ -12,11 +13,16 @@ import android.widget.TextView;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.navigation.Navigation;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
import org.thoughtcrime.securesms.pin.PinState;
import org.thoughtcrime.securesms.util.CommunicationActions;
public final class KbsSplashFragment extends Fragment {
@ -25,6 +31,12 @@ public final class KbsSplashFragment extends Fragment {
private TextView primaryAction;
private TextView secondaryAction;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public @Nullable View onCreateView(@NonNull LayoutInflater inflater,
@Nullable ViewGroup container,
@ -51,12 +63,42 @@ public final class KbsSplashFragment extends Fragment {
description.setMovementMethod(LinkMovementMethod.getInstance());
Toolbar toolbar = view.findViewById(R.id.kbs_splash_toolbar);
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle(null);
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
@Override
public void handleOnBackPressed() { }
});
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
inflater.inflate(R.menu.pin_skip, menu);
}
@Override
public void onPrepareOptionsMenu(@NonNull Menu menu) {
if (RegistrationLockUtil.userHasRegistrationLock(requireContext())) {
menu.clear();
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_pin_learn_more:
onLearnMore();
return true;
case R.id.menu_pin_skip:
onPinSkipped();
return true;
}
return false;
}
private void setUpRegLockEnabled() {
title.setText(R.string.KbsSplashFragment__registration_lock_equals_pin);
description.setText(R.string.KbsSplashFragment__your_registration_lock_is_now_called_a_pin);
@ -80,10 +122,15 @@ public final class KbsSplashFragment extends Fragment {
}
private void onLearnMore() {
Intent intent = new Intent(Intent.ACTION_VIEW);
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.KbsSplashFragment__learn_more_link));
}
intent.setData(Uri.parse(getString(R.string.KbsSplashFragment__learn_more_link)));
startActivity(intent);
private void onPinSkipped() {
PinOptOutDialog.showForSkip(requireContext(),
() -> requireActivity().finish(),
() -> {
PinState.onPinCreateFailure();
requireActivity().finish();
});
}
}

Wyświetl plik

@ -44,6 +44,10 @@ class PinsForAllSchedule implements MegaphoneSchedule {
}
private static boolean isEnabled() {
if (SignalStore.kbsValues().hasOptedOut()) {
return false;
}
if (SignalStore.kbsValues().hasPin()) {
return false;
}

Wyświetl plik

@ -9,6 +9,10 @@ final class SignalPinReminderSchedule implements MegaphoneSchedule {
@Override
public boolean shouldDisplay(int seenCount, long lastSeen, long firstVisible, long currentTime) {
if (SignalStore.kbsValues().hasOptedOut()) {
return false;
}
if (!SignalStore.kbsValues().hasPin()) {
return false;
}

Wyświetl plik

@ -0,0 +1,88 @@
package org.thoughtcrime.securesms.pin;
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.util.ThemeUtil;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
import java.io.IOException;
public final class PinOptOutDialog {
private static final String TAG = Log.tag(PinOptOutDialog.class);
public static void showForSkip(@NonNull Context context, @NonNull Runnable onSuccess, @NonNull Runnable onFailed) {
show(context,
R.string.PinOptOutDialog_warning,
R.string.PinOptOutDialog_skipping_pin_creation_will_create_a_hidden_high_entropy_pin,
R.string.PinOptOutDialog_skip_pin_creation,
true,
onSuccess,
onFailed);
}
public static void showForOptOut(@NonNull Context context, @NonNull Runnable onSuccess, @NonNull Runnable onFailed) {
show(context,
R.string.PinOptOutDialog_warning,
R.string.PinOptOutDialog_disabling_pins_will_create_a_hidden_high_entropy_pin,
R.string.PinOptOutDialog_disable_pin,
false,
onSuccess,
onFailed);
}
private static void show(@NonNull Context context,
@StringRes int titleRes,
@StringRes int bodyRes,
@StringRes int buttonRes,
boolean skip,
@NonNull Runnable onSuccess,
@NonNull Runnable onFailed)
{
AlertDialog dialog = new AlertDialog.Builder(context)
.setTitle(titleRes)
.setMessage(bodyRes)
.setCancelable(true)
.setPositiveButton(buttonRes, (d, which) -> {
d.dismiss();
AlertDialog progress = SimpleProgressDialog.show(context);
SimpleTask.run(() -> {
try {
if (skip) {
PinState.onPinCreationSkipped(context);
} else {
PinState.onPinOptOut(context);
}
return true;
} catch (IOException | UnauthenticatedResponseException e) {
Log.w(TAG, e);
return false;
}
}, success -> {
if (success) {
onSuccess.run();
} else {
onFailed.run();
}
progress.dismiss();
});
})
.setNegativeButton(android.R.string.cancel, (d, which) -> d.dismiss())
.create();
dialog.setOnShowListener(dialogInterface -> {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(ThemeUtil.getThemedColor(context, R.attr.dangerous_button_color));
});
dialog.show();
}
}

Wyświetl plik

@ -127,7 +127,6 @@ public class PinRestoreEntryFragment extends LoggingFragment {
errorLabel.setText(R.string.PinRestoreEntryFragment_incorrect_pin);
helpButton.setVisibility(View.VISIBLE);
skipButton.setVisibility(View.VISIBLE);
} else {
if (triesRemaining.getCount() == 1) {
helpButton.setVisibility(View.VISIBLE);
@ -174,7 +173,6 @@ public class PinRestoreEntryFragment extends LoggingFragment {
cancelSpinning(pinButton);
pinEntry.setEnabled(true);
enableAndFocusPinEntry();
skipButton.setVisibility(View.VISIBLE);
break;
}
}

Wyświetl plik

@ -9,6 +9,7 @@ import androidx.annotation.WorkerThread;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.jobmanager.JobTracker;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.StorageForcePushJob;
import org.thoughtcrime.securesms.keyvalue.KbsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.PinHashing;
@ -17,7 +18,9 @@ import org.thoughtcrime.securesms.lock.v2.PinKeyboardType;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.megaphone.Megaphones;
import org.thoughtcrime.securesms.registration.service.KeyBackupSystemWrongPinException;
import org.thoughtcrime.securesms.util.Hex;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.KbsPinData;
import org.whispersystems.signalservice.api.KeyBackupService;
@ -27,7 +30,6 @@ import org.whispersystems.signalservice.api.kbs.HashedPin;
import org.whispersystems.signalservice.api.kbs.MasterKey;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
import org.whispersystems.signalservice.internal.contacts.entities.TokenResponse;
import org.whispersystems.signalservice.internal.storage.protos.SignalStorage;
import java.io.IOException;
import java.util.Arrays;
@ -124,6 +126,8 @@ public final class PinState {
* Invoked when the user is going through the PIN restoration flow (which is separate from reglock).
*/
public static synchronized void onSignalPinRestore(@NonNull Context context, @NonNull KbsPinData kbsData, @NonNull String pin) {
Log.i(TAG, "onSignalPinRestore()");
SignalStore.kbsValues().setKbsMasterKey(kbsData, pin);
SignalStore.kbsValues().setV2RegistrationLockEnabled(false);
SignalStore.pinValues().resetPinReminders();
@ -152,19 +156,10 @@ public final class PinState {
{
Log.i(TAG, "onPinChangedOrCreated()");
KbsValues kbsValues = SignalStore.kbsValues();
boolean isFirstPin = !kbsValues.hasPin();
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
boolean isFirstPin = !SignalStore.kbsValues().hasPin() || SignalStore.kbsValues().hasOptedOut();
kbsValues.setKbsMasterKey(kbsData, pin);
TextSecurePreferences.clearRegistrationLockV1(context);
SignalStore.pinValues().setKeyboardType(keyboard);
SignalStore.pinValues().resetPinReminders();
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
setPin(context, pin, keyboard);
SignalStore.kbsValues().optIn();
if (isFirstPin) {
Log.i(TAG, "First time setting a PIN. Refreshing attributes to set the 'storage' capability.");
@ -180,11 +175,42 @@ public final class PinState {
* Invoked when PIN creation fails.
*/
public static synchronized void onPinCreateFailure() {
Log.i(TAG, "onPinCreateFailure()");
if (getState() == State.NO_REGISTRATION_LOCK) {
SignalStore.kbsValues().onPinCreateFailure();
}
}
/**
* Invoked when the user has enabled the "PIN opt out" setting.
*/
@WorkerThread
public static synchronized void onPinOptOut(@NonNull Context context)
throws IOException, UnauthenticatedResponseException
{
Log.i(TAG, "onPinOptOutEnabled()");
assertState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED, State.NO_REGISTRATION_LOCK);
optOutOfPin(context);
updateState(buildInferredStateFromOtherFields());
}
/**
* Invoked when the user has chosen to skip PIN creation.
*/
@WorkerThread
public static synchronized void onPinCreationSkipped(@NonNull Context context)
throws IOException, UnauthenticatedResponseException
{
Log.i(TAG, "onPinCreationSkipped()");
assertState(State.NO_REGISTRATION_LOCK);
optOutOfPin(context);
updateState(buildInferredStateFromOtherFields());
}
/**
* Invoked whenever a Signal PIN user enables registration lock.
*/
@ -231,53 +257,6 @@ public final class PinState {
updateState(State.PIN_WITH_REGISTRATION_LOCK_DISABLED);
}
/**
* Called when registration lock is disabled in the settings using the old UI (i.e. no mention of
* Signal PINs).
*/
@WorkerThread
public static synchronized void onDisableLegacyRegistrationLockPreference(@NonNull Context context)
throws IOException, UnauthenticatedResponseException
{
Log.i(TAG, "onDisableRegistrationLockV1()");
assertState(State.REGISTRATION_LOCK_V1);
Log.i(TAG, "Removing v1 registration lock pin from server");
ApplicationDependencies.getSignalServiceAccountManager().removeRegistrationLockV1();
TextSecurePreferences.clearRegistrationLockV1(context);
updateState(State.NO_REGISTRATION_LOCK);
}
/**
* Called when registration lock is enabled in the settings using the old UI (i.e. no mention of
* Signal PINs).
*/
@WorkerThread
public static synchronized void onEnableLegacyRegistrationLockPreference(@NonNull Context context, @NonNull String pin)
throws IOException, UnauthenticatedResponseException
{
Log.i(TAG, "onCompleteRegistrationLockV1Reminder()");
assertState(State.NO_REGISTRATION_LOCK);
KbsValues kbsValues = SignalStore.kbsValues();
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
pinChangeSession.enableRegistrationLock(masterKey);
kbsValues.setKbsMasterKey(kbsData, pin);
kbsValues.setV2RegistrationLockEnabled(true);
TextSecurePreferences.clearRegistrationLockV1(context);
TextSecurePreferences.setRegistrationLockLastReminderTime(context, System.currentTimeMillis());
TextSecurePreferences.setRegistrationLockNextReminderInterval(context, RegistrationLockReminders.INITIAL_INTERVAL);
updateState(buildInferredStateFromOtherFields());
}
/**
* Should only be called by {@link org.thoughtcrime.securesms.migrations.RegistrationPinV2MigrationJob}.
*/
@ -285,6 +264,8 @@ public final class PinState {
public static synchronized void onMigrateToRegistrationLockV2(@NonNull Context context, @NonNull String pin)
throws IOException, UnauthenticatedResponseException
{
Log.i(TAG, "onMigrateToRegistrationLockV2()");
KbsValues kbsValues = SignalStore.kbsValues();
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
@ -300,16 +281,12 @@ public final class PinState {
updateState(buildInferredStateFromOtherFields());
}
public static synchronized boolean shouldShowRegistrationLockV1Reminder() {
return getState() == State.REGISTRATION_LOCK_V1;
}
@WorkerThread
private static void bestEffortRefreshAttributes() {
Optional<JobTracker.JobState> result = ApplicationDependencies.getJobManager().runSynchronously(new RefreshAttributesJob(), TimeUnit.SECONDS.toMillis(10));
if (result.isPresent() && result.get() == JobTracker.JobState.SUCCESS) {
Log.w(TAG, "Attributes were refreshed successfully.");
Log.i(TAG, "Attributes were refreshed successfully.");
} else if (result.isPresent()) {
Log.w(TAG, "Attribute refresh finished, but was not successful. Enqueuing one for later. (Result: " + result.get() + ")");
ApplicationDependencies.getJobManager().add(new RefreshAttributesJob());
@ -344,6 +321,37 @@ public final class PinState {
}
}
@WorkerThread
private static void setPin(@NonNull Context context, @NonNull String pin, @NonNull PinKeyboardType keyboard)
throws IOException, UnauthenticatedResponseException
{
KbsValues kbsValues = SignalStore.kbsValues();
MasterKey masterKey = kbsValues.getOrCreateMasterKey();
KeyBackupService keyBackupService = ApplicationDependencies.getKeyBackupService();
KeyBackupService.PinChangeSession pinChangeSession = keyBackupService.newPinChangeSession();
HashedPin hashedPin = PinHashing.hashPin(pin, pinChangeSession);
KbsPinData kbsData = pinChangeSession.setPin(hashedPin, masterKey);
kbsValues.setKbsMasterKey(kbsData, pin);
TextSecurePreferences.clearRegistrationLockV1(context);
SignalStore.pinValues().setKeyboardType(keyboard);
SignalStore.pinValues().resetPinReminders();
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.PINS_FOR_ALL);
}
@WorkerThread
private static void optOutOfPin(@NonNull Context context)
throws IOException, UnauthenticatedResponseException
{
SignalStore.kbsValues().resetMasterKey();
setPin(context, Hex.toStringCondensed(Util.getSecretBytes(32)), PinKeyboardType.ALPHA_NUMERIC);
SignalStore.kbsValues().optOut();
ApplicationDependencies.getJobManager().add(new StorageForcePushJob());
bestEffortRefreshAttributes();
}
private static @NonNull State assertState(State... allowed) {
State currentState = getState();
@ -358,6 +366,7 @@ public final class PinState {
case REGISTRATION_LOCK_V1: throw new InvalidState_RegistrationLockV1();
case PIN_WITH_REGISTRATION_LOCK_ENABLED: throw new InvalidState_PinWithRegistrationLockEnabled();
case PIN_WITH_REGISTRATION_LOCK_DISABLED: throw new InvalidState_PinWithRegistrationLockDisabled();
case PIN_OPT_OUT: throw new InvalidState_PinOptOut();
default: throw new IllegalStateException("Expected: " + Arrays.toString(allowed) + ", Actual: " + currentState);
}
}
@ -386,6 +395,11 @@ public final class PinState {
boolean v1Enabled = TextSecurePreferences.isV1RegistrationLockEnabled(context);
boolean v2Enabled = kbsValues.isV2RegistrationLockEnabled();
boolean hasPin = kbsValues.hasPin();
boolean optedOut = kbsValues.hasOptedOut();
if (optedOut && !v2Enabled && !v1Enabled) {
return State.PIN_OPT_OUT;
}
if (!v1Enabled && !v2Enabled && !hasPin) {
return State.NO_REGISTRATION_LOCK;
@ -427,7 +441,13 @@ public final class PinState {
/**
* User has a PIN, but registration lock is disabled.
*/
PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled");
PIN_WITH_REGISTRATION_LOCK_DISABLED("pin_with_registration_lock_disabled"),
/**
* The user has opted out of creating a PIN. In this case, we will generate a high-entropy PIN
* on their behalf.
*/
PIN_OPT_OUT("pin_opt_out");
/**
* Using a string key so that people can rename/reorder values in the future without breaking
@ -463,4 +483,5 @@ public final class PinState {
private static class InvalidState_RegistrationLockV1 extends IllegalStateException {}
private static class InvalidState_PinWithRegistrationLockEnabled extends IllegalStateException {}
private static class InvalidState_PinWithRegistrationLockDisabled extends IllegalStateException {}
private static class InvalidState_PinOptOut extends IllegalStateException {}
}

Wyświetl plik

@ -0,0 +1,95 @@
package org.thoughtcrime.securesms.preferences;
import android.content.Intent;
import android.graphics.Color;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.Preference;
import com.google.android.material.snackbar.Snackbar;
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class AdvancedPinPreferenceFragment extends ListSummaryPreferenceFragment {
private static final String PREF_ENABLE = "pref_pin_enable";
private static final String PREF_DISABLE = "pref_pin_disable";
@Override
public void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
}
@Override
public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
addPreferencesFromResource(R.xml.preferences_advanced_pin);
}
@Override
public void onResume() {
super.onResume();
updatePreferenceState();
}
@Override
public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == CreateKbsPinActivity.REQUEST_NEW_PIN && resultCode == CreateKbsPinActivity.RESULT_OK) {
Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_created, Snackbar.LENGTH_LONG).setTextColor(Color.WHITE).show();
}
}
private void updatePreferenceState() {
Preference enable = this.findPreference(PREF_ENABLE);
Preference disable = this.findPreference(PREF_DISABLE);
if (SignalStore.kbsValues().hasOptedOut()) {
enable.setVisible(true);
disable.setVisible(false);
enable.setOnPreferenceClickListener(preference -> {
onPreferenceChanged(true);
return true;
});
} else {
enable.setVisible(false);
disable.setVisible(true);
disable.setOnPreferenceClickListener(preference -> {
onPreferenceChanged(false);
return true;
});
}
((ApplicationPreferencesActivity) getActivity()).getSupportActionBar().setTitle(R.string.preferences__advanced_pin_settings);
}
private void onPreferenceChanged(boolean enabled) {
boolean hasRegistrationLock = TextSecurePreferences.isV1RegistrationLockEnabled(requireContext()) ||
SignalStore.kbsValues().isV2RegistrationLockEnabled();
if (!enabled && hasRegistrationLock) {
new AlertDialog.Builder(requireContext())
.setMessage(R.string.ApplicationPreferencesActivity_pins_are_required_for_registration_lock)
.setCancelable(true)
.setPositiveButton(android.R.string.ok, (d, which) -> d.dismiss())
.show();
} else if (!enabled) {
PinOptOutDialog.showForOptOut(requireContext(),
() -> {
updatePreferenceState();
Snackbar.make(requireView(), R.string.ApplicationPreferencesActivity_pin_disabled, Snackbar.LENGTH_SHORT).setTextColor(Color.WHITE).show();
},
() -> Toast.makeText(requireContext(), R.string.ApplicationPreferencesActivity_failed_to_disable_pins_try_again_later, Toast.LENGTH_LONG).show());
} else {
startActivityForResult(CreateKbsPinActivity.getIntentForPinCreate(requireContext()), CreateKbsPinActivity.REQUEST_NEW_PIN);
}
}
}

Wyświetl plik

@ -24,16 +24,23 @@ import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactIdentityManager;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.KbsValues;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
import org.thoughtcrime.securesms.pin.PinOptOutDialog;
import org.thoughtcrime.securesms.pin.PinState;
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.thoughtcrime.securesms.util.views.SimpleProgressDialog;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
import org.whispersystems.signalservice.internal.contacts.crypto.UnauthenticatedResponseException;
import java.io.IOException;
@ -43,6 +50,7 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
private static final String PUSH_MESSAGING_PREF = "pref_toggle_push_messaging";
private static final String SUBMIT_DEBUG_LOG_PREF = "pref_submit_debug_logs";
private static final String INTERNAL_PREF = "pref_internal";
private static final String ADVANCED_PIN_PREF = "pref_advanced_pin_settings";
private static final int PICK_IDENTITY_CONTACT = 1;
@ -56,6 +64,17 @@ public class AdvancedPreferenceFragment extends CorrectedPreferenceFragment {
submitDebugLog.setOnPreferenceClickListener(new SubmitDebugLogListener());
submitDebugLog.setSummary(getVersion(getActivity()));
Preference pinSettings = this.findPreference(ADVANCED_PIN_PREF);
pinSettings.setOnPreferenceClickListener(preference -> {
requireActivity().getSupportFragmentManager()
.beginTransaction()
.setCustomAnimations(R.anim.slide_from_end, R.anim.slide_to_start, R.anim.slide_from_start, R.anim.slide_to_end)
.replace(android.R.id.content, new AdvancedPinPreferenceFragment())
.addToBackStack(null)
.commit();
return false;
});
Preference internalPreference = this.findPreference(INTERNAL_PREF);
internalPreference.setVisible(FeatureFlags.internalUser());
internalPreference.setOnPreferenceClickListener(preference -> {

Wyświetl plik

@ -119,9 +119,10 @@ public class AppProtectionPreferenceFragment extends CorrectedPreferenceFragment
SwitchPreferenceCompat signalPinReminders = (SwitchPreferenceCompat) this.findPreference(PinValues.PIN_REMINDERS_ENABLED);
SwitchPreferenceCompat registrationLockV2 = (SwitchPreferenceCompat) this.findPreference(KbsValues.V2_LOCK_ENABLED);
if (SignalStore.kbsValues().hasPin()) {
if (SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) {
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinUpdateListener());
signalPinCreateChange.setTitle(R.string.preferences_app_protection__change_your_pin);
signalPinReminders.setEnabled(true);
registrationLockV2.setEnabled(true);
} else {
signalPinCreateChange.setOnPreferenceClickListener(new KbsPinCreateListener());

Wyświetl plik

@ -11,6 +11,13 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.Toolbar
android:id="@+id/kbs_pin_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent" />
<TextView
android:id="@+id/edit_kbs_pin_title"
android:layout_width="match_parent"

Wyświetl plik

@ -5,6 +5,13 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.Toolbar
android:id="@+id/kbs_splash_toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="@+id/kbs_splash_image"
android:layout_width="0dp"

Wyświetl plik

@ -103,7 +103,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/PinRestoreEntryFragment_skip"
android:visibility="gone"
style="@style/Button.Borderless.Registration"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"

Wyświetl plik

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/menu_pin_learn_more"
android:title="@string/KbsSplashFragment__learn_more_about_pins"
app:showAsAction="never"/>
<item
android:id="@+id/menu_pin_skip"
android:title="@string/KbsSplashFragment__skip_pin_creation"
app:showAsAction="never"/>
</menu>

Wyświetl plik

@ -226,6 +226,8 @@
<attr name="message_request_text_color_primary" format="color" />
<attr name="message_request_text_color_secondary" format="color" />
<attr name="dangerous_button_color" format="color" />
<attr name="pref_icon_tint" format="color"/>
<attr name="pref_divider" format="reference" />

Wyświetl plik

@ -53,6 +53,10 @@
<string name="ApplicationPreferencesActivity_privacy_summary">Screen lock %1$s, Registration lock %2$s</string>
<string name="ApplicationPreferencesActivity_privacy_summary_screen_lock">Screen lock %1$s</string>
<string name="ApplicationPreferencesActivity_appearance_summary">Theme %1$s, Language %2$s</string>
<string name="ApplicationPreferencesActivity_pins_are_required_for_registration_lock">PINs are required for registration lock. To disable PINs, please first disable registration lock.</string>
<string name="ApplicationPreferencesActivity_failed_to_disable_pins_try_again_later">Failed to disable PINs. Try again later.</string>
<string name="ApplicationPreferencesActivity_pin_created">Pin created.</string>
<string name="ApplicationPreferencesActivity_pin_disabled">Pin disabled.</string>
<!-- AppProtectionPreferenceFragment -->
<plurals name="AppProtectionPreferenceFragment_minutes">
@ -953,6 +957,14 @@
<string name="PinRestoreLockedFragment_create_new_pin">Create new PIN</string>
<string name="PinRestoreLockedFragment_learn_more_url" translatable="false">https://support.signal.org/hc/articles/360007059792</string>
<!-- PinOptOutDialog -->
<string name="PinOptOutDialog_warning">Warning</string>
<string name="PinOptOutDialog_skipping_pin_creation_will_create_a_hidden_high_entropy_pin">Skipping PIN creation will create a hidden, high-entropy PIN associated with your account that is unrecoverable. When you re-register Signal you will lose all data unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled.</string>
<string name="PinOptOutDialog_skip_pin_creation">Skip PIN creation</string>
<string name="PinOptOutDialog_disable_pin_title">Disable PIN?</string>
<string name="PinOptOutDialog_disabling_pins_will_create_a_hidden_high_entropy_pin">Disabling PIN will create a hidden, high-entropy PIN associated with your account that is unrecoverable. When you re-register Signal you will lose all data unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled.</string>
<string name="PinOptOutDialog_disable_pin">Disable PIN</string>
<!-- RatingManager -->
<string name="RatingManager_rate_this_app">Rate this app</string>
<string name="RatingManager_if_you_enjoy_using_this_app_please_take_a_moment">If you enjoy using this app, please take a moment to help us by rating it.</string>
@ -1892,10 +1904,15 @@
<string name="preferences__dark_theme">Dark</string>
<string name="preferences__appearance">Appearance</string>
<string name="preferences__theme">Theme</string>
<string name="preferences__disable_pin">Disable PIN</string>
<string name="preferences__enable_pin">Enable PIN</string>
<string name="preferences__disabling_pins_will_create_a_hidden_high_entropy_pin">Disabling PIN will create a hidden, high-entropy PIN associated with your account that is unrecoverable. When you re-register Signal you will lose all data unless you manually back up and restore. You can not turn on Registration Lock while the PIN is disabled.</string>
<string name="preferences__pins_keep_information_stored_with_signal_encrypted_so_only_you_can_access_it">PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall. You wont need your PIN to open the app.</string>
<string name="preferences__system_default">System default</string>
<string name="preferences__default">Default</string>
<string name="preferences__language">Language</string>
<string name="preferences__signal_messages_and_calls">Signal messages and calls</string>
<string name="preferences__advanced_pin_settings">Advanced PIN settings</string>
<string name="preferences__free_private_messages_and_calls">Free private messages and calls to Signal users</string>
<string name="preferences__submit_debug_log">Submit debug log</string>
<string name="preferences__support_wifi_calling">\'WiFi Calling\' compatibility mode</string>
@ -2151,7 +2168,7 @@
<string name="CreateKbsPinFragment__create_a_new_pin">Create a new PIN</string>
<string name="CreateKbsPinFragment__you_can_choose_a_new_pin_as_long_as_this_device_is_registered">You can change your PIN as long as this device is registered.</string>
<string name="CreateKbsPinFragment__create_your_pin">Create your PIN</string>
<string name="CreateKbsPinFragment__pins_keep_information_stored_with_signal_encrypted">PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall Signal.</string>
<string name="CreateKbsPinFragment__pins_keep_information_stored_with_signal_encrypted">PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall. You won\'t need your PIN to open the app.</string>
<string name="CreateKbsPinFragment__choose_a_stronger_pin">Choose a stronger PIN</string>
<!-- ConfirmKbsPinFragment -->
@ -2165,7 +2182,7 @@
<!-- KbsSplashFragment -->
<string name="KbsSplashFragment__introducing_pins">Introducing PINs</string>
<string name="KbsSplashFragment__pins_keep_information_stored_with_signal_encrypted">PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall Signal.</string>
<string name="KbsSplashFragment__pins_keep_information_stored_with_signal_encrypted">PINs keep information stored with Signal encrypted so only you can access it. Your profile, settings, and contacts will restore when you reinstall. You won\'t need your PIN to open the app.</string>
<string name="KbsSplashFragment__learn_more">Learn More</string>
<string name="KbsSplashFragment__learn_more_link" translatable="false">https://support.signal.org/hc/articles/360007059792</string>
<string name="KbsSplashFragment__registration_lock_equals_pin">Registration Lock = PIN</string>
@ -2173,6 +2190,8 @@
<string name="KbsSplashFragment__read_more_about_pins">Read more about PINs.</string>
<string name="KbsSplashFragment__update_pin">Update PIN</string>
<string name="KbsSplashFragment__create_your_pin">Create your PIN</string>
<string name="KbsSplashFragment__learn_more_about_pins">Learn more about PINs</string>
<string name="KbsSplashFragment__skip_pin_creation">Skip PIN creation</string>
<!-- KBS Reminder Dialog -->
<string name="KbsReminderDialog__enter_your_signal_pin">Enter your Signal PIN</string>

Wyświetl plik

@ -410,6 +410,8 @@
<item name="tooltip_default_color">@color/core_white</item>
<item name="dangerous_button_color">@color/core_red_highlight</item>
<item name="pref_icon_tint">?attr/icon_tint</item>
<item name="pref_divider">@drawable/preference_divider_light</item>
@ -710,6 +712,8 @@
<item name="tooltip_default_color">@color/core_grey_75</item>
<item name="dangerous_button_color">@color/core_red</item>
<item name="pref_icon_tint">?attr/icon_tint</item>
<item name="pref_divider">@drawable/preference_divider_dark</item>

Wyświetl plik

@ -1,22 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<org.thoughtcrime.securesms.components.SwitchPreferenceCompat
android:defaultValue="false"
android:key="pref_toggle_push_messaging"
android:title="@string/preferences__signal_messages_and_calls"
android:summary="@string/preferences__free_private_messages_and_calls"/>
android:defaultValue="false"
android:key="pref_toggle_push_messaging"
android:title="@string/preferences__signal_messages_and_calls"
android:summary="@string/preferences__free_private_messages_and_calls"/>
<Preference android:key="pref_choose_identity"
android:title="@string/preferences__choose_identity"
android:summary="@string/preferences__choose_your_contact_entry_from_the_contacts_list"/>
<Preference
android:key="pref_advanced_pin_settings"
android:title="@string/preferences__advanced_pin_settings" />
<Preference android:key="pref_submit_debug_logs"
android:title="@string/preferences__submit_debug_log"/>
<Preference
android:key="pref_choose_identity"
android:title="@string/preferences__choose_identity"
android:summary="@string/preferences__choose_your_contact_entry_from_the_contacts_list"/>
<Preference android:key="pref_internal"
android:title="@string/preferences__internal_preferences"
app:isPreferenceVisible="false" />
android:title="@string/preferences__internal_preferences"
app:isPreferenceVisible="false" />
<Preference
android:key="pref_submit_debug_logs"
android:title="@string/preferences__submit_debug_log"/>
</PreferenceScreen>

Wyświetl plik

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
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">
<Preference
android:key="pref_pin_disable"
android:title="@string/preferences__disable_pin"
android:summary="@string/preferences__disabling_pins_will_create_a_hidden_high_entropy_pin"
app:isPreferenceVisible="false"
tools:isPreferenceVisible="true"/>
<Preference
android:key="pref_pin_enable"
android:title="@string/preferences__enable_pin"
android:summary="@string/preferences__pins_keep_information_stored_with_signal_encrypted_so_only_you_can_access_it"
app:isPreferenceVisible="false"
tools:isPreferenceVisible="true"/>
</PreferenceScreen>