summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFabian Henneke <fabian@henneke.me>2020-04-15 17:12:28 +0200
committerHarsh Shandilya <me@msfjarvis.dev>2020-04-15 22:47:41 +0530
commitd6db10e0893af72de6ebc40fd47a775def7ec1d8 (patch)
tree413111d547cbb3a1b0fbc09e2efdeb50e340ff83
parentb633cc1f3dc9acf2e2a2cc11a540375a9d85390b (diff)
Match any path component in StrictDomain FilterMode
-rw-r--r--app/build.gradle1
-rw-r--r--app/src/androidTest/java/com/zeapo/pwdstore/StrictDomainRegexTest.kt48
-rw-r--r--app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt50
-rw-r--r--build.gradle2
-rw-r--r--dependencies.gradle1
5 files changed, 86 insertions, 16 deletions
diff --git a/app/build.gradle b/app/build.gradle
index ac5dcb91..c966ed43 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -121,6 +121,7 @@ dependencies {
// Testing-only dependencies
androidTestImplementation deps.testing.junit
+ androidTestImplementation deps.testing.kotlin_test_junit
androidTestImplementation deps.testing.androidx.runner
androidTestImplementation deps.testing.androidx.rules
androidTestImplementation deps.testing.androidx.junit
diff --git a/app/src/androidTest/java/com/zeapo/pwdstore/StrictDomainRegexTest.kt b/app/src/androidTest/java/com/zeapo/pwdstore/StrictDomainRegexTest.kt
new file mode 100644
index 00000000..c6fa1051
--- /dev/null
+++ b/app/src/androidTest/java/com/zeapo/pwdstore/StrictDomainRegexTest.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+package com.zeapo.pwdstore
+
+import kotlin.test.assertFalse
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+import org.junit.Test as test
+
+private infix fun String.matchedForDomain(domain: String) =
+ SearchableRepositoryViewModel.generateStrictDomainRegex(domain)?.containsMatchIn(this) == true
+
+class StrictDomainRegexTest {
+ @test fun acceptsLiteralDomain() {
+ assertTrue("work/example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
+ assertTrue("example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
+ assertTrue("example.org.gpg" matchedForDomain "example.org")
+ }
+
+ @test fun acceptsSubdomains() {
+ assertTrue("work/www.example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
+ assertTrue("www2.example.org/john.doe@example.org.gpg" matchedForDomain "example.org")
+ assertTrue("www.login.example.org.gpg" matchedForDomain "example.org")
+ }
+
+ @test fun rejectsPhishingAttempts() {
+ assertFalse("example.org.gpg" matchedForDomain "xample.org")
+ assertFalse("login.example.org.gpg" matchedForDomain "xample.org")
+ assertFalse("example.org/john.doe@exmple.org.gpg" matchedForDomain "xample.org")
+ assertFalse("example.org.gpg" matchedForDomain "e/xample.org")
+ }
+
+ @test fun rejectNonGpgComponentMatches() {
+ assertFalse("work/example.org" matchedForDomain "example.org")
+ }
+
+ @test fun rejectsEmailAddresses() {
+ assertFalse("work/notexample.org/john.doe@example.org.gpg" matchedForDomain "example.org")
+ assertFalse("work/notexample.org/john.doe@www.example.org.gpg" matchedForDomain "example.org")
+ assertFalse("work/john.doe@www.example.org/foo.org" matchedForDomain "example.org")
+ }
+
+ @test fun rejectsPathSeparators() {
+ assertNull(SearchableRepositoryViewModel.generateStrictDomainRegex("ex/ample.org"))
+ }
+}
diff --git a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt
index 992a5f79..47170717 100644
--- a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt
+++ b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt
@@ -126,6 +126,28 @@ enum class ListMode {
@FlowPreview
class SearchableRepositoryViewModel(application: Application) : AndroidViewModel(application) {
+ companion object {
+
+ fun generateStrictDomainRegex(domain: String): Regex? {
+ // Valid domains do not contain path separators.
+ if (domain.contains('/'))
+ return null
+ // Matches the start of a path component, which is either the start of the
+ // string or a path separator.
+ val prefix = """(?:^|/)"""
+ val escapedFilter = Regex.escape(domain.replace("/", ""))
+ // Matches either the filter literally or a strict subdomain of the filter term.
+ // We allow a lot of freedom in what a subdomain is, as long as it is not an
+ // email address.
+ val subdomain = """(?:(?:[^/@]+\.)?$escapedFilter)"""
+ // Matches the end of a path component, which is either the literal ".gpg" or a
+ // path separator.
+ val suffix = """(?:\.gpg|/)"""
+ // Match any relative path with a component that is a subdomain of the filter.
+ return Regex(prefix + subdomain + suffix)
+ }
+ }
+
private var _updateCounter = 0
private val updateCounter: Int
get() = _updateCounter
@@ -219,22 +241,18 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel
}
FilterMode.StrictDomain -> {
check(searchAction.listMode == ListMode.FilesOnly) { "Searches with StrictDomain search mode can only list files" }
- prefilteredResultFlow
- .filter { absoluteFile ->
- val file = absoluteFile.relativeTo(root)
- val toMatch =
- directoryStructure.getIdentifierFor(file) ?: return@filter false
- // In strict domain mode, we match
- // * the search term exactly,
- // * subdomains of the search term,
- // * or the search term plus an arbitrary protocol.
- toMatch == searchAction.filter ||
- toMatch.endsWith(".${searchAction.filter}") ||
- toMatch.endsWith("://${searchAction.filter}")
- }
- .map { it.toPasswordItem(root) }
- .toList()
- .sortedWith(itemComparator)
+ val regex = generateStrictDomainRegex(searchAction.filter)
+ if (regex != null) {
+ prefilteredResultFlow
+ .filter { absoluteFile ->
+ regex.containsMatchIn(absoluteFile.relativeTo(root).path)
+ }
+ .map { it.toPasswordItem(root) }
+ .toList()
+ .sortedWith(itemComparator)
+ } else {
+ emptyList()
+ }
}
FilterMode.Fuzzy -> {
prefilteredResultFlow
diff --git a/build.gradle b/build.gradle
index b8da46dc..15c49cf0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -50,6 +50,8 @@ subprojects {
targetSdkVersion versions.targetSdk
versionCode versions.versionCode
versionName versions.versionName
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
diff --git a/dependencies.gradle b/dependencies.gradle
index d57eeeaa..d50129a6 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -67,6 +67,7 @@ ext.deps = [
testing: [
junit: 'junit:junit:4.13',
+ kotlin_test_junit: 'org.jetbrains.kotlin:kotlin-test-junit:1.3.71',
androidx: [
runner: 'androidx.test:runner:1.3.0-alpha05',
rules: 'androidx.test:rules:1.3.0-alpha05',