Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase PIN Enter Delay #14509

Merged
merged 8 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -409,4 +409,7 @@ default void onDarkThemeModeChanged(DarkMode mode) {

boolean isAutoUploadGPlayNotificationShown();
void setAutoUploadGPlayNotificationShown(boolean value);

int getPassCodeDelay();
void setPassCodeDelay(int value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ public final class AppPreferencesImpl implements AppPreferences {
private static final String PREF__AUTO_UPLOAD_GPLAY_WARNING2_SHOWN = "auto_upload_gplay_warning2_shown";
private static final String PREF__AUTO_UPLOAD_GPLAY_NOTIFICATION_SHOWN = "auto_upload_gplay_notification_shown";

private static final String PREF__PASSCODE_DELAY_IN_SECONDS = "passcode_delay_in_seconds";

private static final String LOG_ENTRY = "log_entry";

private final Context context;
Expand Down Expand Up @@ -859,4 +861,14 @@ public boolean isAutoUploadGPlayNotificationShown() {
public void setAutoUploadGPlayNotificationShown(boolean value) {
preferences.edit().putBoolean(PREF__AUTO_UPLOAD_GPLAY_NOTIFICATION_SHOWN, value).apply();
}

@Override
public int getPassCodeDelay() {
return preferences.getInt(PREF__PASSCODE_DELAY_IN_SECONDS, 0);
}

@Override
public void setPassCodeDelay(int value) {
preferences.edit().putInt(PREF__PASSCODE_DELAY_IN_SECONDS, value).apply();
}
}
108 changes: 86 additions & 22 deletions app/src/main/java/com/owncloud/android/ui/activity/PassCodeActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import com.google.android.material.snackbar.Snackbar
import com.nextcloud.android.common.ui.theme.utils.ColorRole
import com.nextcloud.client.di.Injectable
import com.nextcloud.client.preferences.AppPreferences
import com.nextcloud.utils.extensions.setVisibleIf
import com.owncloud.android.R
import com.owncloud.android.authentication.PassCodeManager
import com.owncloud.android.databinding.PasscodelockBinding
Expand Down Expand Up @@ -73,9 +74,11 @@ class PassCodeActivity : AppCompatActivity(), Injectable {
private var passCodeDigits: Array<String> = arrayOf("", "", "", "")
private var confirmingPassCode = false
private var changed = true // to control that only one blocks jump
private var delayInSeconds = 0

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
delayInSeconds = preferences.passCodeDelay
binding = PasscodelockBinding.inflate(layoutInflater)
setContentView(binding.root)

Expand Down Expand Up @@ -221,6 +224,7 @@ class PassCodeActivity : AppCompatActivity(), Injectable {
if (ACTION_CHECK == intent.action) {
if (checkPassCode()) {
preferences.resetPinWrongAttempts()
preferences.passCodeDelay = 0

// / pass code accepted in request, user is allowed to access the app
passCodeManager.updateLockTimestamp()
Expand Down Expand Up @@ -274,6 +278,7 @@ class PassCodeActivity : AppCompatActivity(), Injectable {
binding.header.setText(headerMessage) // TODO check if really needed
binding.explanation.visibility = explanationVisibility // TODO check if really needed
clearBoxes()
increaseAndSaveDelayTime()
showDelay()
}

Expand Down Expand Up @@ -337,33 +342,92 @@ class PassCodeActivity : AppCompatActivity(), Injectable {
finish()
}

private fun showDelay() {
val delayValue = preferences.pinBruteForceDelay()
@Suppress("MagicNumber")
private fun increaseAndSaveDelayTime() {
val maxDelayTimeInSeconds = 300
val delayIncrementation = 15

if (delayInSeconds < maxDelayTimeInSeconds) {
delayInSeconds += delayIncrementation
preferences.passCodeDelay = delayInSeconds
preferences.increasePinWrongAttempts()
}
}

@Suppress("MagicNumber")
private fun getExplanationText(timeInSecond: Int): String {
return when {
timeInSecond < 60 -> resources.getQuantityString(
R.plurals.delay_message,
timeInSecond,
timeInSecond
)
else -> {
val minutes = timeInSecond / 60
val remainingSeconds = timeInSecond % 60

when {
remainingSeconds == 0 -> resources.getQuantityString(
R.plurals.delay_message_minutes,
minutes,
minutes
)
else -> {
val minuteText = resources.getQuantityString(
R.plurals.delay_message_minutes_part,
minutes,
minutes
)
val secondText = resources.getQuantityString(
R.plurals.delay_message_seconds_part,
remainingSeconds,
remainingSeconds
)

val prefixText = "$minuteText $secondText"
getString(R.string.due_to_too_many_wrong_attempts, prefixText)
}
}
}
}
}

if (delayValue <= 0) {
@Suppress("MagicNumber")
private fun showDelay() {
val pinBruteForceCount = preferences.pinBruteForceDelay()
if (pinBruteForceCount <= 0) {
return
}

binding.explanation.setText(R.string.brute_force_delay)
binding.explanation.visibility = View.VISIBLE
binding.txt0.isEnabled = false
binding.txt1.isEnabled = false
binding.txt2.isEnabled = false
binding.txt3.isEnabled = false

lifecycleScope.launch(Dispatchers.IO) {
delay(delayValue * 1000L)

launch(Dispatchers.Main) {
binding.explanation.visibility = View.INVISIBLE
binding.txt0.isEnabled = true
binding.txt1.isEnabled = true
binding.txt2.isEnabled = true
binding.txt3.isEnabled = true

binding.txt0.requestFocus()
binding.txt0.showKeyboard()
enableInputFields(false)

var counter = delayInSeconds
lifecycleScope.launch(Dispatchers.Main) {
while (counter != 0) {
binding.explanation.text = getExplanationText(counter)
delay(1000)
counter -= 1
}

enableInputFields(true)
focusFirstInputField()
}
}

private fun enableInputFields(enabled: Boolean) {
binding.run {
explanation.setVisibleIf(!enabled)
txt0.isEnabled = enabled
txt1.isEnabled = enabled
txt2.isEnabled = enabled
txt3.isEnabled = enabled
}
}

private fun focusFirstInputField() {
binding.run {
txt0.requestFocus()
txt0.showKeyboard()
}
}

Expand Down
25 changes: 24 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,30 @@
<string name="e2e_signature_does_not_match">Signature does not match</string>

<string name="login_url_helper_text">The link to your %1$s web interface when you open it in the browser.</string>
<string name="brute_force_delay">Delayed due to too many wrong attempts</string>

<plurals name="delay_message">
<item quantity="one">Delayed %d second due to too many wrong attempts</item>
<item quantity="other">Delayed %d seconds due to too many wrong attempts</item>
</plurals>

<plurals name="delay_message_minutes">
<item quantity="one">Delayed %d minute due to too many wrong attempts</item>
<item quantity="other">Delayed %d minutes due to too many wrong attempts</item>
</plurals>

<plurals name="delay_message_minutes_part">
<item quantity="one">Delayed %d minute</item>
<item quantity="other">Delayed %d minutes</item>
</plurals>

<plurals name="delay_message_seconds_part">
<item quantity="one">%d second</item>
<item quantity="other">%d seconds</item>
</plurals>

<string name="due_to_too_many_wrong_attempts">%s due to too many wrong attempts</string>


<string name="create">Create</string>
<string name="select_one_template">Please select one template</string>
<string name="choose_template_helper_text">Please choose a template and enter a file name.</string>
Expand Down
Loading