diff --git a/opencloudApp/src/androidTest/java/eu/opencloud/android/settings/security/PassCodeActivityTest.kt b/opencloudApp/src/androidTest/java/eu/opencloud/android/settings/security/PassCodeActivityTest.kt
index 7498d63330..8ddf8e236b 100644
--- a/opencloudApp/src/androidTest/java/eu/opencloud/android/settings/security/PassCodeActivityTest.kt
+++ b/opencloudApp/src/androidTest/java/eu/opencloud/android/settings/security/PassCodeActivityTest.kt
@@ -102,6 +102,7 @@ class PassCodeActivityTest {
}
every { passCodeViewModel.getPassCode() } returns OC_PASSCODE_4_DIGITS
+ every { passCodeViewModel.getPassCodeLength() } returns OC_PASSCODE_4_DIGITS.length
every { passCodeViewModel.getNumberOfPassCodeDigits() } returns 4
every { passCodeViewModel.getNumberOfAttempts() } returns 0
every { passCodeViewModel.getTimeToUnlockLiveData } returns timeToUnlockLiveData
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/AppLockSecretHash.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/AppLockSecretHash.kt
new file mode 100644
index 0000000000..3b3b29e139
--- /dev/null
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/AppLockSecretHash.kt
@@ -0,0 +1,89 @@
+/**
+ * openCloud Android client application
+ *
+ * Copyright (C) 2026 OpenCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eu.opencloud.android.presentation.security
+
+import java.nio.charset.StandardCharsets
+import java.security.MessageDigest
+import java.security.SecureRandom
+import java.util.Base64
+import javax.crypto.SecretKeyFactory
+import javax.crypto.spec.PBEKeySpec
+
+object AppLockSecretHash {
+ private const val PREFIX = "pbkdf2-sha256"
+ private const val VERSION = "v1"
+ private const val ITERATIONS = 120_000
+ private const val SALT_BYTES = 16
+ private const val KEY_LENGTH_BITS = 256
+ private const val FIELD_SEPARATOR = ":"
+ private const val PARTS_COUNT = 5
+ private const val ALGORITHM = "PBKDF2WithHmacSHA256"
+
+ private val secureRandom = SecureRandom()
+
+ fun hash(secret: String): String {
+ val salt = ByteArray(SALT_BYTES).also(secureRandom::nextBytes)
+ val hash = pbkdf2(secret, salt, ITERATIONS)
+
+ return listOf(
+ PREFIX,
+ VERSION,
+ ITERATIONS.toString(),
+ Base64.getEncoder().encodeToString(salt),
+ Base64.getEncoder().encodeToString(hash),
+ ).joinToString(FIELD_SEPARATOR)
+ }
+
+ fun verify(secret: String, storedSecret: String): Boolean =
+ if (isHash(storedSecret)) {
+ verifyHash(secret, storedSecret)
+ } else {
+ MessageDigest.isEqual(
+ secret.toByteArray(StandardCharsets.UTF_8),
+ storedSecret.toByteArray(StandardCharsets.UTF_8)
+ )
+ }
+
+ fun isHash(storedSecret: String): Boolean =
+ storedSecret.startsWith("$PREFIX$FIELD_SEPARATOR$VERSION$FIELD_SEPARATOR")
+
+ private fun verifyHash(secret: String, storedHash: String): Boolean {
+ val parts = storedHash.split(FIELD_SEPARATOR)
+ if (parts.size != PARTS_COUNT || parts[0] != PREFIX || parts[1] != VERSION) return false
+
+ return try {
+ val iterations = parts[2].toInt()
+ val salt = Base64.getDecoder().decode(parts[3])
+ val expectedHash = Base64.getDecoder().decode(parts[4])
+ val actualHash = pbkdf2(secret, salt, iterations)
+ MessageDigest.isEqual(expectedHash, actualHash)
+ } catch (e: IllegalArgumentException) {
+ false
+ }
+ }
+
+ private fun pbkdf2(secret: String, salt: ByteArray, iterations: Int): ByteArray {
+ val spec = PBEKeySpec(secret.toCharArray(), salt, iterations, KEY_LENGTH_BITS)
+ return try {
+ SecretKeyFactory.getInstance(ALGORITHM).generateSecret(spec).encoded
+ } finally {
+ spec.clearPassword()
+ }
+ }
+}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/biometric/BiometricViewModel.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/biometric/BiometricViewModel.kt
index 308eb24485..a9217269d0 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/biometric/BiometricViewModel.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/biometric/BiometricViewModel.kt
@@ -28,6 +28,7 @@ import androidx.biometric.BiometricPrompt
import androidx.lifecycle.ViewModel
import eu.opencloud.android.R
import eu.opencloud.android.data.providers.SharedPreferencesProvider
+import eu.opencloud.android.presentation.security.AppLockSecretHash
import eu.opencloud.android.presentation.security.PREFERENCE_LAST_UNLOCK_TIMESTAMP
import eu.opencloud.android.presentation.security.passcode.PassCodeActivity
import eu.opencloud.android.providers.ContextProvider
@@ -90,11 +91,18 @@ class BiometricViewModel(
fun shouldAskForNewPassCode(): Boolean {
val passCode = preferencesProvider.getString(PassCodeActivity.PREFERENCE_PASSCODE, loadPinFromOldFormatIfPossible())
val passCodeDigits = maxOf(contextProvider.getInt(R.integer.passcode_digits), PassCodeActivity.PASSCODE_MIN_LENGTH)
- return (passCode != null && passCode.length < passCodeDigits)
+ val savedPassCodeDigits = when {
+ passCode == null -> null
+ AppLockSecretHash.isHash(passCode) ->
+ preferencesProvider.getInt(PassCodeActivity.PREFERENCE_PASSCODE_LENGTH, passCodeDigits)
+ else -> passCode.length
+ }
+ return savedPassCodeDigits != null && savedPassCodeDigits < passCodeDigits
}
fun removePassCode() {
preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE)
+ preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE_LENGTH)
preferencesProvider.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, false)
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeActivity.kt
index e9f2022b02..cb5bb21fec 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeActivity.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeActivity.kt
@@ -114,7 +114,7 @@ class PassCodeActivity : AppCompatActivity(), NumberKeyboardListener, EnableBiom
showMessageInSnackbar(message = getString(R.string.biometric_not_available))
}
- numberOfPasscodeDigits = passCodeViewModel.getPassCode()?.length ?: passCodeViewModel.getNumberOfPassCodeDigits()
+ numberOfPasscodeDigits = passCodeViewModel.getPassCodeLength()
passCodeEditTexts = arrayOfNulls(numberOfPasscodeDigits)
// Allow or disallow touches with other visible windows
@@ -195,7 +195,7 @@ class PassCodeActivity : AppCompatActivity(), NumberKeyboardListener, EnableBiom
private fun inflatePasscodeTxtLine() {
val layoutCode = findViewById(R.id.layout_code)
- val numberOfPasscodeDigits = (passCodeViewModel.getPassCode()?.length ?: passCodeViewModel.getNumberOfPassCodeDigits())
+ val numberOfPasscodeDigits = passCodeViewModel.getPassCodeLength()
for (i in 0 until numberOfPasscodeDigits) {
val txt = layoutInflater.inflate(R.layout.passcode_edit_text, layoutCode, false) as EditText
layoutCode.addView(txt)
@@ -484,6 +484,7 @@ class PassCodeActivity : AppCompatActivity(), NumberKeyboardListener, EnableBiom
// NOTE: PREFERENCE_SET_PASSCODE must have the same value as settings_security.xml-->android:key for passcode preference
const val PREFERENCE_SET_PASSCODE = "set_pincode"
const val PREFERENCE_PASSCODE = "PrefPinCode"
+ const val PREFERENCE_PASSCODE_LENGTH = "PrefPinCodeLength"
const val PREFERENCE_MIGRATION_REQUIRED = "PrefMigrationRequired"
// NOTE: This is required to read the legacy pin code format
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeViewModel.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeViewModel.kt
index 11f6518776..ac42a8860a 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeViewModel.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/passcode/PassCodeViewModel.kt
@@ -28,6 +28,7 @@ import androidx.lifecycle.ViewModel
import eu.opencloud.android.R
import eu.opencloud.android.data.providers.SharedPreferencesProvider
import eu.opencloud.android.domain.utils.Event
+import eu.opencloud.android.presentation.security.AppLockSecretHash
import eu.opencloud.android.presentation.security.PREFERENCE_LAST_UNLOCK_ATTEMPT_TIMESTAMP
import eu.opencloud.android.presentation.security.PREFERENCE_LAST_UNLOCK_TIMESTAMP
import eu.opencloud.android.presentation.security.biometric.BiometricActivity
@@ -66,7 +67,7 @@ class PassCodeViewModel(
private var confirmingPassCode = false
init {
- numberOfPasscodeDigits = (getPassCode()?.length ?: getNumberOfPassCodeDigits())
+ numberOfPasscodeDigits = getPassCodeLength()
}
fun onNumberClicked(number: Int) {
@@ -108,11 +109,11 @@ class PassCodeViewModel(
}
private fun actionCheckPasscode() {
- if (checkPassCodeIsValid(passcodeString.toString())) {
+ val enteredPasscode = passcodeString.toString()
+ if (checkPassCodeIsValid(enteredPasscode)) {
// pass code accepted in request, user is allowed to access the app
setLastUnlockTimestamp()
- val passCode = getPassCode()
- if (passCode != null && passCode.length < getNumberOfPassCodeDigits()) {
+ if (getPassCodeLength() < getNumberOfPassCodeDigits()) {
setMigrationRequired(true)
removePassCode()
_status.postValue(Status(PasscodeAction.CHECK, PasscodeType.MIGRATION))
@@ -150,24 +151,40 @@ class PassCodeViewModel(
}
}
- fun getPassCode() = preferencesProvider.getString(PassCodeActivity.PREFERENCE_PASSCODE, loadPinFromOldFormatIfPossible())
+ fun getPassCode(): String? =
+ getStoredPassCode()?.takeUnless(AppLockSecretHash::isHash)
+
+ fun getPassCodeLength(): Int {
+ val storedPassCode = getStoredPassCode()
+ return when {
+ storedPassCode == null -> getNumberOfPassCodeDigits()
+ AppLockSecretHash.isHash(storedPassCode) ->
+ preferencesProvider.getInt(PassCodeActivity.PREFERENCE_PASSCODE_LENGTH, getNumberOfPassCodeDigits())
+ else -> storedPassCode.length
+ }
+ }
fun setPassCode() {
- preferencesProvider.putString(PassCodeActivity.PREFERENCE_PASSCODE, firstPasscode)
- preferencesProvider.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, true)
- numberOfPasscodeDigits = (getPassCode()?.length ?: getNumberOfPassCodeDigits())
+ storePassCode(firstPasscode)
+ numberOfPasscodeDigits = getPassCodeLength()
}
fun removePassCode() {
preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE)
+ preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE_LENGTH)
preferencesProvider.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, false)
- numberOfPasscodeDigits = (getPassCode()?.length ?: getNumberOfPassCodeDigits())
+ numberOfPasscodeDigits = getPassCodeLength()
}
fun checkPassCodeIsValid(passcode: String): Boolean {
- val passCodeString = getPassCode()
- if (passCodeString.isNullOrEmpty()) return false
- return passcode == passCodeString
+ val storedPassCode = getStoredPassCode()
+ if (storedPassCode.isNullOrEmpty()) return false
+
+ val isValid = AppLockSecretHash.verify(passcode, storedPassCode)
+ if (isValid && !AppLockSecretHash.isHash(storedPassCode)) {
+ storePassCode(passcode)
+ }
+ return isValid
}
fun getNumberOfPassCodeDigits(): Int {
@@ -230,6 +247,22 @@ class PassCodeViewModel(
return pinString.ifEmpty { null }
}
+ private fun getStoredPassCode(): String? =
+ preferencesProvider.getString(PassCodeActivity.PREFERENCE_PASSCODE, loadPinFromOldFormatIfPossible())
+
+ private fun storePassCode(passcode: String) {
+ preferencesProvider.putString(PassCodeActivity.PREFERENCE_PASSCODE, AppLockSecretHash.hash(passcode))
+ preferencesProvider.putInt(PassCodeActivity.PREFERENCE_PASSCODE_LENGTH, passcode.length)
+ preferencesProvider.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, true)
+ removeLegacyPinFormat()
+ }
+
+ private fun removeLegacyPinFormat() {
+ for (i in 1..4) {
+ preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE_D + i)
+ }
+ }
+
fun setBiometricsState(enabled: Boolean) {
preferencesProvider.putBoolean(BiometricActivity.PREFERENCE_SET_BIOMETRIC, enabled)
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternActivity.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternActivity.kt
index 9728b1e299..3bc4c41b91 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternActivity.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternActivity.kt
@@ -183,7 +183,7 @@ class PatternActivity : AppCompatActivity(), EnableBiometrics {
}
override fun onProgress(list: List) {
- Timber.d("Pattern Progress %s", PatternLockUtils.patternToString(binding.patternLockView, list))
+ Timber.d("Pattern drawing in progress")
}
override fun onComplete(list: List) {
@@ -205,7 +205,7 @@ class PatternActivity : AppCompatActivity(), EnableBiometrics {
} else {
patternValue = PatternLockUtils.patternToString(binding.patternLockView, list)
}
- Timber.d("Pattern %s", PatternLockUtils.patternToString(binding.patternLockView, list))
+ Timber.d("Pattern drawing completed")
processPattern()
}
diff --git a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternViewModel.kt b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternViewModel.kt
index 112a5e5bb4..0850aa1193 100644
--- a/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternViewModel.kt
+++ b/opencloudApp/src/main/java/eu/opencloud/android/presentation/security/pattern/PatternViewModel.kt
@@ -22,6 +22,7 @@ package eu.opencloud.android.presentation.security.pattern
import androidx.lifecycle.ViewModel
import eu.opencloud.android.data.providers.SharedPreferencesProvider
+import eu.opencloud.android.presentation.security.AppLockSecretHash
import eu.opencloud.android.presentation.security.biometric.BiometricActivity
class PatternViewModel(
@@ -29,7 +30,7 @@ class PatternViewModel(
) : ViewModel() {
fun setPattern(pattern: String) {
- preferencesProvider.putString(PatternActivity.PREFERENCE_PATTERN, pattern)
+ preferencesProvider.putString(PatternActivity.PREFERENCE_PATTERN, AppLockSecretHash.hash(pattern))
preferencesProvider.putBoolean(PatternActivity.PREFERENCE_SET_PATTERN, true)
}
@@ -39,8 +40,16 @@ class PatternViewModel(
}
fun checkPatternIsValid(patternValue: String?): Boolean {
+ if (patternValue == null) return false
+
val savedPattern = preferencesProvider.getString(PatternActivity.PREFERENCE_PATTERN, null)
- return savedPattern != null && savedPattern == patternValue
+ if (savedPattern.isNullOrEmpty()) return false
+
+ val isValid = AppLockSecretHash.verify(patternValue, savedPattern)
+ if (isValid && !AppLockSecretHash.isHash(savedPattern)) {
+ setPattern(patternValue)
+ }
+ return isValid
}
fun setBiometricsState(enabled: Boolean) {
diff --git a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/AppLockSecretHashTest.kt b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/AppLockSecretHashTest.kt
new file mode 100644
index 0000000000..21039ae27a
--- /dev/null
+++ b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/AppLockSecretHashTest.kt
@@ -0,0 +1,46 @@
+/**
+ * openCloud Android client application
+ *
+ * Copyright (C) 2026 OpenCloud GmbH.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package eu.opencloud.android.presentation.viewmodels.security
+
+import eu.opencloud.android.presentation.security.AppLockSecretHash
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+
+class AppLockSecretHashTest {
+
+ @Test
+ fun `hash hides secret and validates matching secret`() {
+ val secret = "1234"
+
+ val storedSecret = AppLockSecretHash.hash(secret)
+
+ assertNotEquals(secret, storedSecret)
+ assertTrue(AppLockSecretHash.isHash(storedSecret))
+ assertTrue(AppLockSecretHash.verify(secret, storedSecret))
+ assertFalse(AppLockSecretHash.verify("4321", storedSecret))
+ }
+
+ @Test
+ fun `verify still accepts legacy plaintext secret`() {
+ assertTrue(AppLockSecretHash.verify("1234", "1234"))
+ assertFalse(AppLockSecretHash.verify("4321", "1234"))
+ }
+}
diff --git a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/BiometricViewModelTest.kt b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/BiometricViewModelTest.kt
index b062e96918..8ac3c01800 100644
--- a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/BiometricViewModelTest.kt
+++ b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/BiometricViewModelTest.kt
@@ -107,6 +107,7 @@ class BiometricViewModelTest : ViewModelTest() {
verify(exactly = 1) {
preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE)
+ preferencesProvider.removePreference(PassCodeActivity.PREFERENCE_PASSCODE_LENGTH)
preferencesProvider.putBoolean(PassCodeActivity.PREFERENCE_SET_PASSCODE, false)
}
}
diff --git a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt
index ca2ffb6083..c3998dbebd 100644
--- a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt
+++ b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PassCodeViewModelTest.kt
@@ -23,6 +23,7 @@ package eu.opencloud.android.presentation.viewmodels.security
import android.os.SystemClock
import eu.opencloud.android.R
import eu.opencloud.android.data.providers.SharedPreferencesProvider
+import eu.opencloud.android.presentation.security.AppLockSecretHash
import eu.opencloud.android.presentation.security.PREFERENCE_LAST_UNLOCK_ATTEMPT_TIMESTAMP
import eu.opencloud.android.presentation.security.PREFERENCE_LAST_UNLOCK_TIMESTAMP
import eu.opencloud.android.presentation.security.passcode.PassCodeViewModel
@@ -30,6 +31,7 @@ import eu.opencloud.android.presentation.viewmodels.ViewModelTest
import eu.opencloud.android.presentation.security.passcode.PassCodeActivity
import eu.opencloud.android.presentation.security.passcode.PassCodeActivity.Companion.PREFERENCE_PASSCODE
import eu.opencloud.android.presentation.security.passcode.PassCodeActivity.Companion.PREFERENCE_PASSCODE_D
+import eu.opencloud.android.presentation.security.passcode.PassCodeActivity.Companion.PREFERENCE_PASSCODE_LENGTH
import eu.opencloud.android.presentation.security.passcode.PassCodeActivity.Companion.PREFERENCE_SET_PASSCODE
import eu.opencloud.android.presentation.security.passcode.PasscodeAction
import eu.opencloud.android.presentation.security.passcode.PasscodeType
@@ -72,6 +74,7 @@ class PassCodeViewModelTest : ViewModelTest() {
for (i in 0..4) {
every { preferencesProvider.getString(PREFERENCE_PASSCODE_D + i, null) } returns passcodeD //loadPinFromOldFormatIfPossible()
}
+ every { preferencesProvider.getInt(PREFERENCE_PASSCODE_LENGTH, any()) } returns (passcode?.length ?: passcodeDigits)
every { preferencesProvider.getInt(PREFERENCE_LOCK_ATTEMPTS, any()) } returns lockAttempts //getNumberOfAttempts()
every { preferencesProvider.getLong(PREFERENCE_LAST_UNLOCK_ATTEMPT_TIMESTAMP, any()) } returns lastUnlockAttempt //getTimeToUnlockLeft()
}
@@ -234,7 +237,11 @@ class PassCodeViewModelTest : ViewModelTest() {
assertEquals(Status(PasscodeAction.CREATE, PasscodeType.CONFIRM), passCodeViewModel.status.value)
verify(exactly = 1) {
- preferencesProvider.putString(PREFERENCE_PASSCODE, any())
+ preferencesProvider.putString(
+ PREFERENCE_PASSCODE,
+ match { it != OC_PASSCODE_4_DIGITS && AppLockSecretHash.verify(OC_PASSCODE_4_DIGITS, it) }
+ )
+ preferencesProvider.putInt(PREFERENCE_PASSCODE_LENGTH, OC_PASSCODE_4_DIGITS.length)
preferencesProvider.putBoolean(PREFERENCE_SET_PASSCODE, true)
}
}
diff --git a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PatternViewModelTest.kt b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PatternViewModelTest.kt
index 2420d60c32..75fc6481df 100644
--- a/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PatternViewModelTest.kt
+++ b/opencloudApp/src/test/java/eu/opencloud/android/presentation/viewmodels/security/PatternViewModelTest.kt
@@ -21,6 +21,7 @@
package eu.opencloud.android.presentation.viewmodels.security
import eu.opencloud.android.data.providers.SharedPreferencesProvider
+import eu.opencloud.android.presentation.security.AppLockSecretHash
import eu.opencloud.android.presentation.security.pattern.PatternActivity
import eu.opencloud.android.presentation.security.pattern.PatternViewModel
import eu.opencloud.android.presentation.viewmodels.ViewModelTest
@@ -51,7 +52,10 @@ class PatternViewModelTest : ViewModelTest() {
patternViewModel.setPattern(pattern)
verify(exactly = 1) {
- preferencesProvider.putString(PatternActivity.PREFERENCE_PATTERN, pattern)
+ preferencesProvider.putString(
+ PatternActivity.PREFERENCE_PATTERN,
+ match { it != pattern && AppLockSecretHash.verify(pattern, it) }
+ )
preferencesProvider.putBoolean(PatternActivity.PREFERENCE_SET_PATTERN, true)
}
}