From b8fdadcedb6e407fd5506f30647a1a043e6dab39 Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 29 Apr 2024 16:01:28 -0500 Subject: [PATCH] BIT-2180: Check htmlInfo for username field (#1318) --- .../autofill/builder/SaveInfoBuilderImpl.kt | 14 ++++++++---- .../data/autofill/util/HtmlInfoExtensions.kt | 22 +++++++++++++++++++ .../data/autofill/util/ViewNodeExtensions.kt | 3 ++- .../autofill/util/ViewNodeExtensionsTest.kt | 18 ++++++++++++++- 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderImpl.kt index 328c97d0b8..0cf0081349 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/builder/SaveInfoBuilderImpl.kt @@ -41,14 +41,20 @@ class SaveInfoBuilderImpl( return if (autofillPartition is AutofillPartition.Login && isInCompatMode) { null } else { - val saveInfoBuilder = SaveInfo + SaveInfo .Builder( autofillPartition.saveType, autofillPartition.requiredSaveIds.toTypedArray(), ) - .setOptionalIds(autofillPartition.optionalSaveIds.toTypedArray()) - if (isInCompatMode) saveInfoBuilder.setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) - saveInfoBuilder.build() + .apply { + // setOptionalIds will throw an IllegalArgumentException if the array is empty + autofillPartition + .optionalSaveIds + .takeUnless { it.isEmpty() } + ?.let { setOptionalIds(it.toTypedArray()) } + if (isInCompatMode) setFlags(SaveInfo.FLAG_SAVE_ON_ALL_VIEWS_INVISIBLE) + } + .build() } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/HtmlInfoExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/HtmlInfoExtensions.kt index 4d378da9d3..03e8d09efd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/HtmlInfoExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/HtmlInfoExtensions.kt @@ -25,6 +25,28 @@ fun HtmlInfo?.isPasswordField(): Boolean = } ?: false +/** + * Whether this [HtmlInfo] represents a username field. + * + * This function is untestable as [HtmlInfo] contains [android.util.Pair] which requires + * instrumentation testing. + */ +@OmitFromCoverage +fun HtmlInfo?.isUsernameField(): Boolean = + this + ?.let { htmlInfo -> + if (htmlInfo.isInputField) { + htmlInfo + .attributes + ?.any { + it.first == "type" && it.second == "email" + } + } else { + false + } + } + ?: false + /** * Whether this [HtmlInfo] represents an input field. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt index f5c71e2f1c..712ab291b9 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensions.kt @@ -183,7 +183,8 @@ fun AssistStructure.ViewNode.isUsernameField( supportedHint == View.AUTOFILL_HINT_EMAIL_ADDRESS || inputType.isUsernameInputType || idEntry?.containsAnyTerms(SUPPORTED_RAW_USERNAME_HINTS) == true || - hint?.containsAnyTerms(SUPPORTED_RAW_USERNAME_HINTS) == true + hint?.containsAnyTerms(SUPPORTED_RAW_USERNAME_HINTS) == true || + htmlInfo.isUsernameField() /** * The website that this [AssistStructure.ViewNode] is a part of representing. diff --git a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt index 45b97b10a2..319a2e2f58 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/autofill/util/ViewNodeExtensionsTest.kt @@ -356,7 +356,7 @@ class ViewNodeExtensionsTest { } @Test - fun `isUsernameField returns true whe supportedHint is AUTOFILL_HINT_USERNAME`() { + fun `isUsernameField returns true when supportedHint is AUTOFILL_HINT_USERNAME`() { // Setup val supportedHint = View.AUTOFILL_HINT_USERNAME @@ -416,6 +416,21 @@ class ViewNodeExtensionsTest { } } + @Test + fun `isUsernameField returns true when htmlInfo isUserNameField is true`() { + every { viewNode.idEntry } returns null + every { viewNode.hint } returns null + every { viewNode.htmlInfo.isUsernameField() } returns true + + // Test + val actual = viewNode.isUsernameField( + supportedHint = null, + ) + + // Verify + assertTrue(actual) + } + @Test fun `website should return URI if domain and scheme are valid`() { // Setup @@ -496,6 +511,7 @@ class ViewNodeExtensionsTest { private fun setupUnsupportedInputFieldViewNode() { every { viewNode.hint } returns null every { viewNode.htmlInfo.isPasswordField() } returns false + every { viewNode.htmlInfo.isUsernameField() } returns false every { viewNode.htmlInfo.isInputField } returns true every { viewNode.idEntry } returns null every { viewNode.autofillHints } returns emptyArray()