diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt index 9f9c784c9..5cb77e581 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/IdCardDataCreator.kt @@ -39,6 +39,7 @@ class IdCardDataCreator { pin1RetryCount: Int = 3, pin2RetryCount: Int = 3, pukRetryCount: Int = 3, + pin1CodeChanged: Boolean = true, pin2CodeChanged: Boolean = true, ): IdCardData = IdCardData( @@ -49,6 +50,7 @@ class IdCardDataCreator { pin1RetryCount = pin1RetryCount, pin2RetryCount = pin2RetryCount, pukRetryCount = pukRetryCount, + pin1CodeChanged = pin1CodeChanged, pin2CodeChanged = pin2CodeChanged, ) diff --git a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt index 377195801..b8f4b26b5 100644 --- a/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt +++ b/app/src/androidTest/kotlin/ee/ria/DigiDoc/viewmodel/IdCardViewModelTest.kt @@ -206,6 +206,7 @@ class IdCardViewModelTest { 3, 3, true, + true, ) `when`(idCardService.data(anyOrNull())).thenReturn(idCardData) @@ -280,6 +281,7 @@ class IdCardViewModelTest { 3, 3, true, + true, ) `when`(idCardService.data(anyOrNull())).thenReturn(idCardData) @@ -342,6 +344,7 @@ class IdCardViewModelTest { 2, 3, true, + true, ) `when`(idCardService.data(anyOrNull())).thenReturn(idCardData) @@ -409,6 +412,7 @@ class IdCardViewModelTest { 1, 3, true, + true, ) `when`(idCardService.data(anyOrNull())).thenReturn(idCardData) @@ -476,6 +480,7 @@ class IdCardViewModelTest { 0, 3, true, + true, ) `when`(idCardService.data(anyOrNull())).thenReturn(idCardData) @@ -541,6 +546,7 @@ class IdCardViewModelTest { 3, 0, 3, + true, false, ) diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt index 2f12b3365..8959def29 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/DecryptScreen.kt @@ -281,6 +281,7 @@ fun DecryptScreen( isValidToDecrypt = { isValid -> isValidToDecrypt = isValid }, + onCourierCardDetected = {}, decryptAction = { action -> decryptAction = { isDecrypting = true diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt index 2173c468a..aabda53ec 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/fragment/screen/SignatureInputScreen.kt @@ -368,6 +368,10 @@ fun SignatureInputScreen( isValidToSign = { isValid -> isValidToSign = isValid }, + onCourierCardDetected = {}, + onCourierCardDialogDismissed = { + navController.navigateUp() + }, signAction = { action -> signAction = { isSigning = true @@ -408,6 +412,9 @@ fun SignatureInputScreen( isValidToSign = { isValid -> isValidToSign = isValid }, + onCourierCardDialogDismissed = { + navController.navigateUp() + }, signAction = { action -> signAction = action }, diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt index 5f0510438..091691103 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/MyEidScreen.kt @@ -117,9 +117,10 @@ fun MyEidScreen( val isPin2Blocked = idCardData?.pin2RetryCount == 0 val isPukBlocked = idCardData?.pukRetryCount == 0 val isPin2Activated = idCardData?.pin2CodeChanged == true + val isCourierCard = idCardData?.personalData != null && idCardData?.pin1CodeChanged == false - val alphaForPin1BlockedState = getAlphaForBlockedState(isPin1Blocked && isPukBlocked) - val alphaForPin2BlockedState = getAlphaForBlockedState(isPin2Blocked && isPukBlocked) + val alphaForPin1BlockedState = getAlphaForBlockedState((isPin1Blocked && isPukBlocked) || isCourierCard) + val alphaForPin2BlockedState = getAlphaForBlockedState((isPin2Blocked && isPukBlocked) || isCourierCard) val alphaForPukBlockedState = getAlphaForBlockedState(isPukBlocked) val selectedMyEidTabIndex = rememberSaveable { mutableIntStateOf(0) } @@ -378,6 +379,7 @@ fun MyEidScreen( ), isPinBlocked = isPin1Blocked, isPukBlocked = isPukBlocked, + isNotActivated = isCourierCard, forgotPinText = if (isPin1Blocked) { stringResource( @@ -434,6 +436,12 @@ fun MyEidScreen( style = MaterialTheme.typography.bodySmall, ) } + if (isCourierCard) { + CourierCardWarningText( + modifier = modifier, + testTag = "myEidCourierCardPin1DescriptionText", + ) + } } } item { @@ -459,6 +467,7 @@ fun MyEidScreen( ), isPinBlocked = isPin2Blocked, isPukBlocked = isPukBlocked, + isNotActivated = isCourierCard, forgotPinText = if (isPin2Blocked) { stringResource( @@ -484,7 +493,12 @@ fun MyEidScreen( }, ) - if (!isPin2Activated) { + if (isCourierCard) { + CourierCardWarningText( + modifier = modifier, + testTag = "myEidCourierCardPin2DescriptionText", + ) + } else if (!isPin2Activated) { Text( modifier = modifier @@ -670,4 +684,36 @@ fun MyEidScreen( ) } +@Composable +private fun CourierCardWarningText( + modifier: Modifier, + testTag: String, +) { + val message = stringResource(R.string.id_card_courier_warning_message) + val linkText = stringResource(R.string.id_card_courier_activate_button) + val linkUrl = stringResource(R.string.id_card_courier_activate_url) + val linkWord = stringResource(R.string.link) + HrefDynamicText( + modifier = + modifier + .fillMaxWidth() + .focusable(true) + .testTag(testTag) + .semantics { + contentDescription = "$message $linkText, $linkWord, $linkUrl" + }, + text1 = message, + text2 = null, + linkText = linkText, + linkUrl = linkUrl, + newLineBeforeLink = true, + textStyle = + TextStyle( + color = MaterialTheme.colorScheme.error, + fontSize = MaterialTheme.typography.bodySmall.fontSize, + textAlign = TextAlign.Start, + ), + ) +} + fun getAlphaForBlockedState(isBlocked: Boolean) = if (!isBlocked) 1f else 0.7f diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt index 6fa75cf17..e41097802 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/myeid/pinandcertificate/MyEidPinAndCertificateView.kt @@ -78,6 +78,7 @@ fun MyEidPinAndCertificateView( linkUrl: String = "", isPinBlocked: Boolean = false, isPukBlocked: Boolean = false, + isNotActivated: Boolean = false, showForgotPin: Boolean = true, forgotPinText: String = "", onForgotPinClick: (() -> Unit)? = null, @@ -130,6 +131,7 @@ fun MyEidPinAndCertificateView( modifier = modifier .weight(1f) + .padding(vertical = XSPadding) .focusable() .semantics(mergeDescendants = true) { this.contentDescription = "$title. $subtitle".lowercase() @@ -179,7 +181,7 @@ fun MyEidPinAndCertificateView( verticalAlignment = Alignment.CenterVertically, ) { OutlinedButton( - enabled = !isPukBlocked, + enabled = !isPukBlocked && !isNotActivated, onClick = onForgotPinClick, modifier = modifier @@ -209,7 +211,7 @@ fun MyEidPinAndCertificateView( } Button( - enabled = !isPinBlocked, + enabled = !isPinBlocked && !isNotActivated, onClick = onChangePinClick ?: {}, modifier = modifier diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/dialog/CourierCardActivationDialog.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/dialog/CourierCardActivationDialog.kt new file mode 100644 index 000000000..5c15006e8 --- /dev/null +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/shared/dialog/CourierCardActivationDialog.kt @@ -0,0 +1,121 @@ +/* + * Copyright 2017 - 2026 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +@file:Suppress("PackageName", "FunctionName") + +package ee.ria.DigiDoc.ui.component.shared.dialog + +import androidx.compose.foundation.background +import androidx.compose.foundation.focusable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.testTagsAsResourceId +import androidx.compose.ui.text.style.TextAlign +import ee.ria.DigiDoc.R +import ee.ria.DigiDoc.ui.component.shared.CancelAndOkButtonRow +import ee.ria.DigiDoc.ui.component.shared.InvisibleElement +import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding +import ee.ria.DigiDoc.ui.theme.buttonRoundCornerShape +import kotlinx.coroutines.delay + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun CourierCardActivationDialog( + modifier: Modifier = Modifier, + onDismiss: () -> Unit, +) { + val focusRequester = remember { FocusRequester() } + Box(modifier = modifier.fillMaxSize()) { + BasicAlertDialog( + modifier = + Modifier + .clip(buttonRoundCornerShape) + .background(MaterialTheme.colorScheme.surface), + onDismissRequest = onDismiss, + ) { + LaunchedEffect(Unit) { + delay(100) + focusRequester.requestFocus() + } + Surface( + modifier = + Modifier + .padding(SPadding) + .wrapContentHeight() + .wrapContentWidth() + .verticalScroll(rememberScrollState()), + ) { + Column( + modifier = + Modifier + .semantics { testTagsAsResourceId = true } + .testTag("courierCardActivationDialogContainer"), + ) { + Text( + modifier = + Modifier + .padding(SPadding) + .fillMaxWidth() + .focusRequester(focusRequester) + .focusable(), + text = stringResource(R.string.id_card_courier_must_activate_to_sign), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onBackground, + textAlign = TextAlign.Start, + ) + CancelAndOkButtonRow( + okButtonTestTag = "courierCardDialogOkButton", + cancelButtonTestTag = "courierCardDialogCancelButton", + cancelButtonClick = {}, + okButtonClick = onDismiss, + cancelButtonTitle = R.string.cancel_button, + okButtonTitle = R.string.ok_button, + cancelButtonContentDescription = stringResource(R.string.cancel_button).lowercase(), + okButtonContentDescription = stringResource(R.string.ok_button).lowercase(), + showCancelButton = false, + ) + } + } + } + InvisibleElement(modifier = Modifier) + } +} diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt index 8282cbf71..582c95480 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/IdCardView.kt @@ -97,6 +97,7 @@ import ee.ria.DigiDoc.ui.component.shared.HrefMessageDialog import ee.ria.DigiDoc.ui.component.shared.InvisibleElement import ee.ria.DigiDoc.ui.component.shared.RoleDataView import ee.ria.DigiDoc.ui.component.shared.SecurePinTextField +import ee.ria.DigiDoc.ui.component.shared.dialog.CourierCardActivationDialog import ee.ria.DigiDoc.ui.theme.Dimensions.LPadding import ee.ria.DigiDoc.ui.theme.Dimensions.MSPadding import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding @@ -140,6 +141,8 @@ fun IdCardView( isValidToSign: (Boolean) -> Unit = {}, isAuthenticated: (Boolean, IdCardData) -> Unit, isValidToDecrypt: (Boolean) -> Unit = {}, + onCourierCardDetected: (Boolean) -> Unit = {}, + onCourierCardDialogDismissed: () -> Unit = {}, signAction: (() -> Unit) -> Unit = {}, decryptAction: (() -> Unit) -> Unit = {}, cancelAction: (() -> Unit) -> Unit = {}, @@ -175,10 +178,16 @@ fun IdCardView( "" } - val idCardStatusReadyToSign = idCardData?.personalData != null && !isSigning && !isAuthenticating && !isDecrypting + val isCourierCard = + idCardStatus == SmartCardReaderStatus.CARD_DETECTED && + idCardData?.personalData != null && + idCardData?.pin1CodeChanged == false + val idCardStatusReadyToSign = + idCardData?.personalData != null && !isSigning && !isAuthenticating && !isDecrypting && !isCourierCard val shouldHandleError by idCardViewModel.shouldHandleError.collectAsState() val showErrorDialog = rememberSaveable { mutableStateOf(false) } + val showCourierCardDialog = rememberSaveable { mutableStateOf(false) } var isDataLoadingStarted by rememberSaveable { mutableStateOf(false) } var showLoadingIndicator by rememberSaveable { mutableStateOf(false) } @@ -237,7 +246,6 @@ fun IdCardView( val statusMessageFocusRequester = remember { FocusRequester() } val readyToSignFocusRequester = remember { FocusRequester() } val pinCodeFocusRequester = remember { FocusRequester() } - var isValid by rememberSaveable { mutableStateOf(false) } val clearButtonText = stringResource(R.string.clear_text) @@ -423,6 +431,20 @@ fun IdCardView( } } + LaunchedEffect(isCourierCard) { + if (isCourierCard) { + isValidToSign(false) + isValidToDecrypt(false) + onCourierCardDetected(true) + if (identityAction == IdentityAction.SIGN) { + showCourierCardDialog.value = true + } + } else { + showCourierCardDialog.value = false + onCourierCardDetected(false) + } + } + LaunchedEffect(Unit, idCardData, isValid, isSigning, isDecrypting, isAuthenticating, idCardStatusMessage) { if (idCardData?.personalData == null || (isValid && isSigning) || @@ -531,6 +553,13 @@ fun IdCardView( } } + if (showCourierCardDialog.value) { + CourierCardActivationDialog(onDismiss = { + showCourierCardDialog.value = false + onCourierCardDialogDismissed() + }) + } + Column( modifier = modifier diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt index 70f8f0142..ed79d5eb5 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/NFCView.kt @@ -106,6 +106,7 @@ import ee.ria.DigiDoc.ui.component.shared.HrefMessageDialog import ee.ria.DigiDoc.ui.component.shared.InvisibleElement import ee.ria.DigiDoc.ui.component.shared.RoleDataView import ee.ria.DigiDoc.ui.component.shared.SecurePinTextField +import ee.ria.DigiDoc.ui.component.shared.dialog.CourierCardActivationDialog import ee.ria.DigiDoc.ui.component.support.textFieldValueSaver import ee.ria.DigiDoc.ui.theme.Dimensions.MSPadding import ee.ria.DigiDoc.ui.theme.Dimensions.SPadding @@ -149,6 +150,7 @@ fun NFCView( isSupported: (Boolean) -> Unit = {}, isValidToSign: (Boolean) -> Unit = {}, isValidToDecrypt: (Boolean) -> Unit = {}, + onCourierCardDialogDismissed: () -> Unit = {}, showPinField: Boolean = true, isValidToAuthenticate: (Boolean) -> Unit, signAction: (() -> Unit) -> Unit = {}, @@ -185,6 +187,7 @@ fun NFCView( } var errorText by remember { mutableStateOf("") } val showErrorDialog = rememberSaveable { mutableStateOf(false) } + val showCourierCardDialog = rememberSaveable { mutableStateOf(false) } val focusManager = LocalFocusManager.current val saveFormParams = { if (shouldRememberMe) { @@ -199,6 +202,8 @@ fun NFCView( val canNumberFocusRequester = remember { FocusRequester() } val pinNumberFocusRequester = remember { FocusRequester() } + + val isCourierCard = personalData?.personalData != null && personalData?.pin1CodeChanged == false val canNumberWithInvisibleSpaces = TextFieldValue(addInvisibleElement(canNumber.text)) val pinCode = remember { mutableStateOf(byteArrayOf()) } @@ -235,6 +240,10 @@ fun NFCView( } } + LaunchedEffect(Unit) { + nfcViewModel.resetIdCardUserData() + } + LaunchedEffect(nfcViewModel.shouldResetPIN) { nfcViewModel.shouldResetPIN.asFlow().collect { bool -> bool.let { @@ -320,10 +329,14 @@ fun NFCView( .asFlow() .filterNotNull() .filterNot { it == 0 } - .collect { + .collect { error -> withContext(Main) { nfcViewModel.resetErrorState() - showErrorDialog.value = true + if (error == R.string.id_card_courier_must_activate_to_sign) { + showCourierCardDialog.value = true + } else { + showErrorDialog.value = true + } } } } @@ -352,11 +365,30 @@ fun NFCView( } } + LaunchedEffect(isCourierCard) { + if (isCourierCard) { + isValidToSign(false) + isValidToDecrypt(false) + if (identityAction == IdentityAction.SIGN) { + showCourierCardDialog.value = true + } + } else { + showCourierCardDialog.value = false + } + } + if (errorText.isNotEmpty()) { showMessage(errorText) errorText = "" } + if (showCourierCardDialog.value) { + CourierCardActivationDialog(onDismiss = { + showCourierCardDialog.value = false + onCourierCardDialogDismissed() + }) + } + if (showErrorDialog.value) { var text1Arg: Int? = null val text2 = null diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt index b262ff2df..df75977e8 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/ui/component/signing/SigningNavigation.kt @@ -26,7 +26,6 @@ import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent import android.content.res.Configuration -import android.util.Log import android.widget.Toast import androidx.activity.compose.BackHandler import androidx.activity.compose.rememberLauncherForActivityResult diff --git a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt index ca4f9f520..3be39ec97 100644 --- a/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt +++ b/app/src/main/kotlin/ee/ria/DigiDoc/viewmodel/NFCViewModel.kt @@ -57,7 +57,6 @@ import ee.ria.DigiDoc.utils.pin.PinCodeUtil.isPINLengthValid import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.debugLog import ee.ria.DigiDoc.utilsLib.logging.LoggingUtil.Companion.errorLog import ee.ria.libdigidocpp.ExternalSigner -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -229,11 +228,14 @@ class NFCViewModel nfcSmartCardReaderManager.startDiscovery(activity) { nfcReader, exc -> if ((nfcReader != null) && (exc == null)) { try { - CoroutineScope(Main).launch { - _message.postValue(R.string.signature_update_nfc_detected) - } + _message.postValue(R.string.signature_update_nfc_detected) val card = TokenWithPace.create(nfcReader) card.tunnel(canNumber) + val pin1ChangedFlagValue = card.pinChangedFlag(CodeType.PIN1) + if (pin1ChangedFlagValue == 1) { + _dialogError.postValue(R.string.id_card_courier_must_activate_to_sign) + return@startDiscovery + } val signerCert = card.certificate(CertificateType.SIGNING) debugLog( logTag, @@ -260,11 +262,9 @@ class NFCViewModel signatureArray, ) - CoroutineScope(Main).launch { - _shouldResetPIN.postValue(true) - _signStatus.postValue(true) - _signedContainer.postValue(container) - } + _shouldResetPIN.postValue(true) + _signStatus.postValue(true) + _signedContainer.postValue(container) } catch (ex: SmartCardReaderException) { _signStatus.postValue(false) @@ -388,9 +388,7 @@ class NFCViewModel nfcSmartCardReaderManager.startDiscovery(activity) { nfcReader, exc -> if ((nfcReader != null) && (exc == null)) { try { - CoroutineScope(Main).launch { - _message.postValue(R.string.signature_update_nfc_detected) - } + _message.postValue(R.string.signature_update_nfc_detected) val card = TokenWithPace.create(nfcReader) card.tunnel(canNumber) @@ -414,11 +412,9 @@ class NFCViewModel if (pin1Code.isNotEmpty()) { Arrays.fill(pin1Code, 0.toByte()) } - CoroutineScope(Main).launch { - _shouldResetPIN.postValue(true) - _decryptStatus.postValue(true) - _cryptoContainer.postValue(decryptedContainer) - } + _shouldResetPIN.postValue(true) + _decryptStatus.postValue(true) + _cryptoContainer.postValue(decryptedContainer) } catch (ex: SmartCardReaderException) { _decryptStatus.postValue(false) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index bb14e4803..855d1aa18 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -647,6 +647,10 @@ %1$s on blokeeritud, kuna %1$s-koodi on sisestatud 3 korda valesti. Selle ID-kaardiga allkirjastamine ei ole veel võimalik. Allkirjastamiseks tuleb %1$s-koodi muuta. + ID-kaardiga isikutuvastamine ja allkirjastamine ei ole veel võimalik. ID-kaardi kasutamiseks tuleb see aktiveerida Politsei- ja Piirivalveameti iseteeninduses. + Aktiveeri ID-kaart + https://www.politsei.ee/et/iseteenindus/ + Allkirjastamiseks tuleb ID-kaart aktiveerida. %1$s on blokeeritud, kuna %1$s-koodi on sisestatud 3 korda valesti. Tühista blokeering, et %1$s taas kasutada. PUK on blokeeritud, kuna PUK-koodi on sisestatud 3 korda valesti. PUK-koodi ei saa ise lahti blokeerida.\n\nKuigi PUK-kood on blokeeritud, saab kõiki eID võimalusi kasutada, välja arvatud PUK-koodi vajavaid.\n\nUue PUK-koodi saamiseks külasta klienditeeninduspunkti, kust saad uue koodiümbriku uute koodidega. https://www.id.ee/artikkel/id-kaardi-pin-ja-puk-koodide-muutmine/ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf64e3f8b..b6bf54abe 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -647,6 +647,10 @@ %1$s is blocked, because %1$s has been inserted 3 times incorrectly. Signing with the ID-card isn\'t possible yet. %1$s code must be changed in order to sign. + Authentication and signing with the ID-card isn\'t possible yet. ID-card must be activated in the Police and Border Guard Board\'s self-service portal in order to use it. + Activate ID-card + https://www.politsei.ee/en/self-service-portal/ + The ID-card must be activated in order to sign. %1$s has been blocked because %1$s code has been entered incorrectly 3 times. Unblock to reuse %1$s. PUK has been blocked because PUK code has been entered incorrectly 3 times. You can not unblock the PUK code yourself.\n\nAs long as the PUK code is blocked, all eID options can be used, except transactions that need PUK code.\n\nPlease visit the service center to obtain new codes. https://www.id.ee/en/article/changing-id-card-pin-codes-and-puk-code/ diff --git a/id-card-lib/id-lib/id-card-lib-debug.aar b/id-card-lib/id-lib/id-card-lib-debug.aar index c3df9294a..855b490dd 100644 Binary files a/id-card-lib/id-lib/id-card-lib-debug.aar and b/id-card-lib/id-lib/id-card-lib-debug.aar differ diff --git a/id-card-lib/id-lib/id-card-lib-release.aar b/id-card-lib/id-lib/id-card-lib-release.aar index 9ae349e84..2183b893d 100644 Binary files a/id-card-lib/id-lib/id-card-lib-release.aar and b/id-card-lib/id-lib/id-card-lib-release.aar differ diff --git a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt index 5c86a7bb3..1dcd0f2fd 100644 --- a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt +++ b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/model/IdCardData.kt @@ -33,5 +33,6 @@ data class IdCardData( val pin1RetryCount: Int, val pin2RetryCount: Int, val pukRetryCount: Int, + val pin1CodeChanged: Boolean, val pin2CodeChanged: Boolean, ) diff --git a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt index 734c785fd..14243a45a 100644 --- a/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt +++ b/id-card-lib/src/main/kotlin/ee/ria/DigiDoc/domain/service/IdCardServiceImpl.kt @@ -73,7 +73,8 @@ class IdCardServiceImpl val pin1RetryCounter = token.codeRetryCounter(CodeType.PIN1) val pin2RetryCounter = token.codeRetryCounter(CodeType.PIN2) val pukRetryCounter = token.codeRetryCounter(CodeType.PUK) - val pin2CodeChanged = token.pinChangedFlag() + val pin1CodeChanged = token.pinChangedFlag(CodeType.PIN1) + val pin2CodeChanged = token.pinChangedFlag(CodeType.PIN2) val authCertificate = ExtendedCertificate.create(authenticationCertificateData, certificateService) val signCertificate = ExtendedCertificate.create(signingCertificateData, certificateService) @@ -86,6 +87,7 @@ class IdCardServiceImpl pin1RetryCount = pin1RetryCounter, pin2RetryCount = pin2RetryCounter, pukRetryCount = pukRetryCounter, + pin1CodeChanged = pin1CodeChanged == 1, pin2CodeChanged = pin2CodeChanged == 1, ) } diff --git a/id-card-lib/src/test/kotlin/ee/ria/DigiDoc/domain/model/IdCardDataTest.kt b/id-card-lib/src/test/kotlin/ee/ria/DigiDoc/domain/model/IdCardDataTest.kt new file mode 100644 index 000000000..e33b9719e --- /dev/null +++ b/id-card-lib/src/test/kotlin/ee/ria/DigiDoc/domain/model/IdCardDataTest.kt @@ -0,0 +1,103 @@ +/* + * Copyright 2017 - 2026 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +@file:Suppress("PackageName") + +package ee.ria.DigiDoc.domain.model + +import ee.ria.DigiDoc.common.model.EIDType +import ee.ria.DigiDoc.common.model.ExtendedCertificate +import ee.ria.DigiDoc.idcard.PersonalData +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Test +import org.mockito.Mockito.mock +import org.mockito.Mockito.`when` + +class IdCardDataTest { + private val personalData: PersonalData = mock(PersonalData::class.java) + private val authCertificate: ExtendedCertificate = mock(ExtendedCertificate::class.java) + private val signCertificate: ExtendedCertificate = mock(ExtendedCertificate::class.java) + + init { + `when`(authCertificate.type).thenReturn(EIDType.ID_CARD) + `when`(signCertificate.type).thenReturn(EIDType.ID_CARD) + } + + private fun buildIdCardData( + pin1CodeChanged: Boolean, + pin2CodeChanged: Boolean, + ) = IdCardData( + type = EIDType.ID_CARD, + personalData = personalData, + authCertificate = authCertificate, + signCertificate = signCertificate, + pin1RetryCount = 3, + pin2RetryCount = 3, + pukRetryCount = 3, + pin1CodeChanged = pin1CodeChanged, + pin2CodeChanged = pin2CodeChanged, + ) + + @Test + fun idCardData_pin1CodeChanged_whenFlagIsOne_isTrue() { + val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = true) + assertTrue(data.pin1CodeChanged) + } + + @Test + fun idCardData_pin1CodeChanged_whenFlagIsZero_isFalse() { + val data = buildIdCardData(pin1CodeChanged = false, pin2CodeChanged = false) + assertFalse(data.pin1CodeChanged) + } + + @Test + fun idCardData_pin2CodeChanged_whenFlagIsOne_isTrue() { + val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = true) + assertTrue(data.pin2CodeChanged) + } + + @Test + fun idCardData_pin2CodeChanged_whenFlagIsZero_isFalse() { + val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = false) + assertFalse(data.pin2CodeChanged) + } + + @Test + fun idCardData_courierCard_pin1NotChanged_isCourierCard() { + val data = buildIdCardData(pin1CodeChanged = false, pin2CodeChanged = false) + val isCourierCard = !data.pin1CodeChanged + assertTrue(isCourierCard) + } + + @Test + fun idCardData_activatedCard_pin1Changed_isNotCourierCard() { + val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = true) + val isCourierCard = !data.pin1CodeChanged + assertFalse(isCourierCard) + } + + @Test + fun idCardData_regularThalesCard_pin1ChangedPin2Not_isNotCourierCard() { + // Regular Thales card: PIN1 has been changed, only PIN2 is unused + val data = buildIdCardData(pin1CodeChanged = true, pin2CodeChanged = false) + val isCourierCard = !data.pin1CodeChanged + assertFalse(isCourierCard) + } +}