Merge branch 'develop' into fix/1270-verification-request-timeout

This commit is contained in:
Benoit Marty
2026-08-17 18:49:02 +02:00
committed by GitHub
245 changed files with 4174 additions and 1811 deletions
@@ -15,7 +15,6 @@ import com.freeletics.flowredux.dsl.FlowReduxStateMachine
import io.element.android.features.verifysession.impl.util.andLogStateChange
import io.element.android.features.verifysession.impl.util.logReceivedEvents
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.core.data.tryOrNull
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
@@ -24,6 +23,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationRequest
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.delay
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.timeout
@@ -106,10 +106,13 @@ class OutgoingVerificationStateMachine(
// If a key backup exists, wait until it's restored or a timeout happens
val hasBackup = encryptionService.doesBackupExistOnServer().getOrNull().orFalse()
if (hasBackup) {
tryOrNull {
encryptionService.recoveryStateStateFlow.filter { it == RecoveryState.ENABLED }
try {
encryptionService.recoveryStateStateFlow
.filter { it == RecoveryState.ENABLED || it == RecoveryState.DISABLED || it == RecoveryState.INCOMPLETE }
.timeout(10.seconds)
.first()
} catch (_: TimeoutCancellationException) {
// The backup was not restored in time, proceed to the Completed state anyway
}
}
state.override { State.Completed.andLogStateChange() }
@@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"لا يمكنك التأكيد؟"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"إنشاء مفتاح استرداد جديد"</string>
<string name="screen_identity_confirmation_subtitle">"اختر طريقة التحقق لإعداد المراسلة الآمنة."</string>
<string name="screen_identity_confirmation_title">"تأكيد هويتك الرقمية"</string>
<string name="screen_identity_confirmation_use_another_device">"استخدام جهاز آخر"</string>
<string name="screen_identity_confirmation_use_recovery_key">"استخدام مفتاح الاسترداد"</string>
<string name="screen_identity_confirmed_subtitle">"يمكنك الآن قراءة الرسائل أو إرسالها بأمان، ويمكن لأي شخص تدردش معه أن يثق بهذا الجهاز أيضًا."</string>
<string name="screen_identity_confirmed_title">"تم التحقق من الجهاز"</string>
<string name="screen_identity_use_another_device">"استخدام جهاز آخر"</string>
<string name="screen_identity_waiting_on_other_device">"في انتظار الجهاز الآخر…"</string>
<string name="screen_session_verification_cancelled_subtitle">"شيء ما لا يبدو سليماً. إما مهلة الطلب قد انتهت أو أن الطلب رُفِض."</string>
<string name="screen_session_verification_compare_emojis_subtitle">"تأكد من تطابق الرموز التعبيرية أدناه مع تلك الظاهرة في جلستك الأخرى."</string>
<string name="screen_session_verification_compare_emojis_title">"قارن الرموز التعبيرية"</string>
<string name="screen_session_verification_compare_emojis_user_subtitle">"أكّد أن الرموز التعبيرية أدناه تطابق الرموز المعروضة على جهاز المستخدم الآخر."</string>
<string name="screen_session_verification_compare_numbers_subtitle">"أكّد أن الأرقام أدناه تطابق الأرقام المعروضة في جلستك الأخرى."</string>
<string name="screen_session_verification_compare_numbers_title">"قارن الأرقام"</string>
<string name="screen_session_verification_complete_subtitle">"تم التحقق من جلستك الحالية. فيها وصول إلى رسائلك المشفّرة والمستخدمون الآخرون يرونها موثوقة."</string>
<string name="screen_session_verification_complete_user_subtitle">"يمكنك الآن الوثوق بالهوية الرقمية لهذا المستخدم عند إرسال الرسائل أو تلقيها."</string>
<string name="screen_session_verification_device_verified">"تم التحقق من الجهاز"</string>
<string name="screen_session_verification_enter_recovery_key">"أدخل مفتاح الاسترداد"</string>
<string name="screen_session_verification_failed_subtitle">"إما أن الطلب انتهت مهلته، أو تم رفضه، أو حدث عدم تطابق في التحقق."</string>
<string name="screen_session_verification_open_existing_session_subtitle">"أثبت هويتك ليصبح لديك وصول إلى رسائلك المشفّرة السابقة."</string>
<string name="screen_session_verification_open_existing_session_title">"افتح جلسة موجودة"</string>
<string name="screen_session_verification_positive_button_canceled">"إعادة محاولة عملية التحقق"</string>
<string name="screen_session_verification_positive_button_initial">"جاهز"</string>
<string name="screen_session_verification_positive_button_verifying_ongoing">"بانتظار التطابق"</string>
<string name="screen_session_verification_ready_subtitle">"قارِن مجموعة فريدة من الرموز التعبيرية."</string>
<string name="screen_session_verification_request_accepted_subtitle">"قارن بين الرموز التعبيرية المميزة، متأكداً من ورودها في نفس الترتيب."</string>
<string name="screen_session_verification_request_details_timestamp">"تم تسجيل الدخول"</string>
<string name="screen_session_verification_request_failure_subtitle">"إما أن الطلب انتهت مهلته، أو تم رفضه، أو حدث عدم تطابق في التحقق."</string>
<string name="screen_session_verification_request_failure_title">"فشل التحقق"</string>
<string name="screen_session_verification_request_footer">"لا تتابع إلا إذا كنت أنت من بدأ هذا التحقق."</string>
<string name="screen_session_verification_request_subtitle">"تحقّق من الجهاز الآخر للحفاظ على أمان سجل رسائلك."</string>
<string name="screen_session_verification_request_success_subtitle">"تم التحقق من جلستك الحالية. فيها وصول إلى رسائلك المشفّرة والمستخدمون الآخرون يرونها موثوقة."</string>
<string name="screen_session_verification_request_success_title">"تم التحقق من الجهاز"</string>
<string name="screen_session_verification_request_title">"تم طلب التحقق"</string>
<string name="screen_session_verification_they_dont_match">"لا يتطابقون"</string>
<string name="screen_session_verification_they_match">"يتطابقون"</string>
<string name="screen_session_verification_use_another_device_subtitle">"تأكّد من أن التطبيق مفتوح على الجهاز الآخر قبل بدء التحقق من هنا."</string>
<string name="screen_session_verification_use_another_device_title">"افتح التطبيق على جهاز آخر تم التحقق منه"</string>
<string name="screen_session_verification_user_initiator_subtitle">"لمزيد من الأمان، تحقّق من هذا المستخدم بمقارنة مجموعة من الرموز التعبيرية على أجهزتكما. افعل ذلك باستخدام وسيلة موثوقة للتواصل."</string>
<string name="screen_session_verification_user_initiator_title">"هل تريد التحقق من هذا المستخدم؟"</string>
<string name="screen_session_verification_user_responder_subtitle">"لمزيد من الأمان، يريد مستخدم آخر التحقق من هويتك الرقمية. ستُعرض لك مجموعة من الرموز التعبيرية لمقارنتها."</string>
<string name="screen_session_verification_waiting_another_device_subtitle">"من المفترض أن تظهر نافذة منبثقة على الجهاز الآخر. ابدأ التحقق من هناك الآن."</string>
<string name="screen_session_verification_waiting_another_device_title">"ابدأ التحقق على الجهاز الآخر"</string>
<string name="screen_session_verification_waiting_other_device_title">"ابدأ التحقق على الجهاز الآخر"</string>
<string name="screen_session_verification_waiting_other_user_title">"في انتظار المستخدم الآخر"</string>
<string name="screen_session_verification_waiting_subtitle">"بمجرد القبول، ستتمكن من متابعة التحقق."</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"اِقبل طلب بدء عملية التحقق في جلستك الأخرة للاستمرار."</string>
<string name="screen_session_verification_waiting_to_accept_title">"بانتظار قبول الطلب"</string>
<string name="screen_signout_in_progress_dialog_content">"تجري إزالة الجهاز…"</string>
</resources>
@@ -2,8 +2,8 @@
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="screen_identity_confirmation_cannot_confirm">"¿No puedes confirmar?"</string>
<string name="screen_identity_confirmation_create_new_recovery_key">"Crear una nueva clave de recuperación"</string>
<string name="screen_identity_confirmation_subtitle">"Verifica este dispositivo para configurar la mensajería segura."</string>
<string name="screen_identity_confirmation_title">"Confirma que eres tú"</string>
<string name="screen_identity_confirmation_subtitle">"Elige cómo verificar para configurar la mensajería segura."</string>
<string name="screen_identity_confirmation_title">"Confirma tu identidad digital"</string>
<string name="screen_identity_confirmation_use_another_device">"Usar otro dispositivo"</string>
<string name="screen_identity_confirmation_use_recovery_key">"Usar clave de recuperación"</string>
<string name="screen_identity_confirmed_subtitle">"Ahora puedes leer o enviar mensajes de forma segura y cualquier persona con la que chatees también puede confiar en este dispositivo."</string>
@@ -17,7 +17,7 @@
<string name="screen_session_verification_compare_numbers_subtitle">"Confirma que los números que aparecen a continuación coinciden con los mostrados en tu otra sesión."</string>
<string name="screen_session_verification_compare_numbers_title">"Comparar números"</string>
<string name="screen_session_verification_complete_subtitle">"Ahora puedes leer o enviar mensajes de forma segura en tu otro dispositivo."</string>
<string name="screen_session_verification_complete_user_subtitle">"Ahora puedes confiar en la identidad de este usuario al enviar o recibir mensajes."</string>
<string name="screen_session_verification_complete_user_subtitle">"Ahora puedes confiar en la identidad digital de este usuario al enviar o recibir mensajes."</string>
<string name="screen_session_verification_device_verified">"Dispositivo verificado"</string>
<string name="screen_session_verification_enter_recovery_key">"Introduce la clave de recuperación"</string>
<string name="screen_session_verification_failed_subtitle">"O bien se agotó el tiempo de solicitud, se rechazó la solicitud o hubo una discrepancia en la verificación."</string>
@@ -42,7 +42,7 @@
<string name="screen_session_verification_use_another_device_title">"Abre la aplicación en otro dispositivo verificado"</string>
<string name="screen_session_verification_user_initiator_subtitle">"Para mayor seguridad, verifica a este usuario comparando un conjunto de emojis en vuestros dispositivos. Hazlo utilizando una forma de comunicación de confianza."</string>
<string name="screen_session_verification_user_initiator_title">"¿Verificar a este usuario?"</string>
<string name="screen_session_verification_user_responder_subtitle">"Para mayor seguridad, otro usuario quiere verificar tu identidad. Se te mostrará un conjunto de emojis para compararlos."</string>
<string name="screen_session_verification_user_responder_subtitle">"Para mayor seguridad, otro usuario quiere verificar tu identidad digital. Se te mostrará un conjunto de emojis para compararlos."</string>
<string name="screen_session_verification_waiting_another_device_subtitle">"Deberías ver una ventana emergente en el otro dispositivo. Inicia ahora la verificación desde allí."</string>
<string name="screen_session_verification_waiting_another_device_title">"Iniciar la verificación en el otro dispositivo"</string>
<string name="screen_session_verification_waiting_other_device_title">"Iniciar la verificación en el otro dispositivo"</string>
@@ -50,5 +50,5 @@
<string name="screen_session_verification_waiting_subtitle">"Una vez aceptada, podrás continuar con la verificación."</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"Acepta la solicitud para iniciar el proceso de verificación en tu otra sesión para continuar."</string>
<string name="screen_session_verification_waiting_to_accept_title">"A la espera de aceptar la solicitud"</string>
<string name="screen_signout_in_progress_dialog_content">"Cerrando sesión…"</string>
<string name="screen_signout_in_progress_dialog_content">"Eliminando dispositivo…"</string>
</resources>
@@ -13,11 +13,14 @@
<string name="screen_session_verification_cancelled_subtitle">"يه چيزي درست به نظر نمياد یا زمان درخواست به پایان رسید یا درخواست رد شد."</string>
<string name="screen_session_verification_compare_emojis_subtitle">"تأیید تطابق شکلک‌های زیر با شکلک‌های نشان داده شده روی افزارهٔ دیگرتان."</string>
<string name="screen_session_verification_compare_emojis_title">"مقایسهٔ شکلک‌ها"</string>
<string name="screen_session_verification_compare_emojis_user_subtitle">"تأیید کنید که ایموجی‌های زیر با ایموجی‌های نمایش داده شده در دستگاه کاربر دیگر مطابقت دارند."</string>
<string name="screen_session_verification_compare_numbers_subtitle">"تأیید کنید که اعداد زیر با اعداد نشان داده شده در جلسه دیگر شما مطابقت دارند."</string>
<string name="screen_session_verification_compare_numbers_title">"مقایسهٔ اعداد"</string>
<string name="screen_session_verification_complete_subtitle">"اکنون می‌توانید روی افزارهٔ دیگرتان با امنیت پیام فرستاده و بخوانید."</string>
<string name="screen_session_verification_complete_user_subtitle">"اکنون می‌توانید هنگام ارسال یا دریافت پیام، به هویت دیجیتال این کاربر اعتماد کنید."</string>
<string name="screen_session_verification_device_verified">"افزاره تأیید شده"</string>
<string name="screen_session_verification_enter_recovery_key">"ورود کلید بازیابی"</string>
<string name="screen_session_verification_failed_subtitle">"یا مهلت درخواست تمام شده، یا درخواست رد شده، یا عدم تطابق تأیید وجود داشته است."</string>
<string name="screen_session_verification_open_existing_session_subtitle">"برای دسترسی به تاریخچه پیام‌های رمزگذاری‌شده‌تان، ثابت کنید که خودتان هستید."</string>
<string name="screen_session_verification_open_existing_session_title">"گشودن نشستی موجود"</string>
<string name="screen_session_verification_positive_button_canceled">"تلاش برای تأیید دوباره"</string>
@@ -26,11 +29,25 @@
<string name="screen_session_verification_ready_subtitle">"مقایسهٔ مجموعه‌ای یکتا از شکلک‌ها."</string>
<string name="screen_session_verification_request_accepted_subtitle">"شکلک‌ها را مقایسه کنید، از ترتیب نمایش آنان نیز مطمئن شوید."</string>
<string name="screen_session_verification_request_details_timestamp">"وارد شده"</string>
<string name="screen_session_verification_request_failure_subtitle">"یا مهلت درخواست تمام شده، یا درخواست رد شده، یا عدم تطابق تأیید وجود داشته است."</string>
<string name="screen_session_verification_request_failure_title">"صحت‌سنجی شکست خورد"</string>
<string name="screen_session_verification_request_footer">"فقط در صورتی ادامه دهید که خودتان این تأیید را آغاز کرده باشید."</string>
<string name="screen_session_verification_request_subtitle">"برای ایمن نگه داشتن تاریخچه پیام‌هایتان، دستگاه دیگر را تأیید کنید."</string>
<string name="screen_session_verification_request_success_subtitle">"اکنون می‌توانید روی افزارهٔ دیگرتان با امنیت پیام فرستاده و بخوانید."</string>
<string name="screen_session_verification_request_success_title">"افزاره تأیید شده"</string>
<string name="screen_session_verification_request_title">"درخواست تأیید"</string>
<string name="screen_session_verification_they_dont_match">"مطابق نیستند"</string>
<string name="screen_session_verification_they_match">"مطابقند"</string>
<string name="screen_session_verification_use_another_device_subtitle">"قبل از شروع تأیید از اینجا، مطمئن شوید که برنامه را در دستگاه دیگر باز کرده‌اید."</string>
<string name="screen_session_verification_use_another_device_title">"برنامه را روی دستگاه تأیید شده دیگری باز کنید"</string>
<string name="screen_session_verification_user_initiator_subtitle">"برای امنیت بیشتر، با مقایسه مجموعه‌ای از ایموجی‌ها در دستگاه‌های خود، این کاربر را تأیید کنید. این کار را با استفاده از یک روش ارتباطی مطمئن انجام دهید."</string>
<string name="screen_session_verification_user_initiator_title">"این کاربر را تایید می‌کنید؟"</string>
<string name="screen_session_verification_user_responder_subtitle">"برای امنیت بیشتر، اگر کاربر دیگری بخواهد هویت دیجیتالی شما را تأیید کند، مجموعه‌ای از ایموجی‌ها برای مقایسه به شما نشان داده می‌شود."</string>
<string name="screen_session_verification_waiting_another_device_subtitle">"باید یک پنجره‌ی پاپ‌آپ روی دستگاه دیگر ببینید. اکنون تأیید را از آنجا شروع کنید."</string>
<string name="screen_session_verification_waiting_another_device_title">"شروع تأیید در دستگاه دیگر"</string>
<string name="screen_session_verification_waiting_other_device_title">"شروع تأیید در دستگاه دیگر"</string>
<string name="screen_session_verification_waiting_other_user_title">"در انتظار کاربر دیگر"</string>
<string name="screen_session_verification_waiting_subtitle">"پس از پذیرش، می‌توانید به تأیید ادامه دهید."</string>
<string name="screen_session_verification_waiting_to_accept_subtitle">"برای ادامه، درخواست شروع فرآیند تأیید را در جلسه دیگر خود بپذیرید."</string>
<string name="screen_session_verification_waiting_to_accept_title">"منظر پذیرش درخواست"</string>
<string name="screen_signout_in_progress_dialog_content">"برداشتن افزاره…"</string>
@@ -16,6 +16,7 @@ import io.element.android.features.verifysession.impl.outgoing.OutgoingVerificat
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.verification.SessionVerificationData
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
@@ -238,6 +239,85 @@ class OutgoingVerificationPresenterTest {
}
}
@Test
fun `present - When verification is approved and a backup exists but recovery is disabled, the flow completes`() = runTest {
val service = unverifiedSessionService(
requestUserVerificationLambda = { },
startSasVerificationLambda = { },
approveVerificationLambda = { },
)
val encryptionService = FakeEncryptionService().apply {
givenDoesBackupExistOnServerResult(Result.success(true))
recoveryStateStateFlow.value = RecoveryState.DISABLED
}
val presenter = createOutgoingVerificationPresenter(
service = service,
verificationRequest = anOutgoingUserVerificationRequest(),
encryptionService = encryptionService,
)
presenter.test {
val state = requestVerificationAndAwaitVerifyingState(service)
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
service.emitVerificationFlowState(VerificationFlowState.DidFinish)
assertThat(awaitItem().step).isEqualTo(Step.Completed)
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - When verification is approved and a backup exists but recovery is incomplete, the flow completes`() = runTest {
val service = unverifiedSessionService(
requestUserVerificationLambda = { },
startSasVerificationLambda = { },
approveVerificationLambda = { },
)
val encryptionService = FakeEncryptionService().apply {
givenDoesBackupExistOnServerResult(Result.success(true))
recoveryStateStateFlow.value = RecoveryState.INCOMPLETE
}
val presenter = createOutgoingVerificationPresenter(
service = service,
verificationRequest = anOutgoingUserVerificationRequest(),
encryptionService = encryptionService,
)
presenter.test {
val state = requestVerificationAndAwaitVerifyingState(service)
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
service.emitVerificationFlowState(VerificationFlowState.DidFinish)
assertThat(awaitItem().step).isEqualTo(Step.Completed)
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - When verification is approved but the recovery state is not determined, the flow completes after the timeout`() = runTest {
val service = unverifiedSessionService(
requestUserVerificationLambda = { },
startSasVerificationLambda = { },
approveVerificationLambda = { },
)
val encryptionService = FakeEncryptionService().apply {
givenDoesBackupExistOnServerResult(Result.success(true))
}
val presenter = createOutgoingVerificationPresenter(
service = service,
verificationRequest = anOutgoingUserVerificationRequest(),
encryptionService = encryptionService,
)
presenter.test {
val state = requestVerificationAndAwaitVerifyingState(service)
state.eventSink(OutgoingVerificationViewEvents.ConfirmVerification)
assertThat(awaitItem().step).isInstanceOf(Step.Verifying::class.java)
service.emitVerificationFlowState(VerificationFlowState.DidFinish)
// The recovery state never leaves UNKNOWN, so the 10 seconds timeout fires and the flow completes anyway
this@runTest.advanceUntilIdle()
assertThat(awaitItem().step).isEqualTo(Step.Completed)
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - When verification is declined, the flow is canceled`() = runTest {
val service = unverifiedSessionService(