Skip to content

Commit ba746bd

Browse files
authored
Keep HTTP auth credentials when site is fireproof(ed) (#1066)
* Keep HTTP auth credentials when site is fireproof(ed) * Remove trailing comma * Address code review comments from cmonforte * Removed androidx.security:security-crypto unused dependency * Supressed warnings in WebViewHttpAuthStoreTest * Removed default param value from WebViewHttpAuthStore and use the dispatcherProvider * Removed GlobalScope from WebDataManager * Supressed warnings in clearFormDataCompat * Get dispatcherProvider from DI graph * Removed unnecessary VisibleForTesting * Fixed the issue with `whenAddLoginDetectionThenJSInterfaceAdded` * Removed the migration of httpauth db as it is not necessary and it ensures db compatibility * Also provided WebViewDatabase instance thru DI * Provided solution for all Android APIs * Merged runBlocking + withContext
1 parent e02df16 commit ba746bd

File tree

17 files changed

+443
-107
lines changed

17 files changed

+443
-107
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"formatVersion": 1,
3+
"database": {
4+
"version": 1,
5+
"identityHash": "90a13fae33bb9bd0053f91a508fbf5a8",
6+
"entities": [
7+
{
8+
"tableName": "httpauth",
9+
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`_id` INTEGER PRIMARY KEY AUTOINCREMENT, `host` TEXT, `realm` TEXT, `username` TEXT, `password` TEXT)",
10+
"fields": [
11+
{
12+
"fieldPath": "_id",
13+
"columnName": "_id",
14+
"affinity": "INTEGER",
15+
"notNull": false
16+
},
17+
{
18+
"fieldPath": "host",
19+
"columnName": "host",
20+
"affinity": "TEXT",
21+
"notNull": false
22+
},
23+
{
24+
"fieldPath": "realm",
25+
"columnName": "realm",
26+
"affinity": "TEXT",
27+
"notNull": false
28+
},
29+
{
30+
"fieldPath": "username",
31+
"columnName": "username",
32+
"affinity": "TEXT",
33+
"notNull": false
34+
},
35+
{
36+
"fieldPath": "password",
37+
"columnName": "password",
38+
"affinity": "TEXT",
39+
"notNull": false
40+
}
41+
],
42+
"primaryKey": {
43+
"columnNames": [
44+
"_id"
45+
],
46+
"autoGenerate": true
47+
},
48+
"indices": [],
49+
"foreignKeys": []
50+
}
51+
],
52+
"views": [],
53+
"setupQueries": [
54+
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
55+
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '90a13fae33bb9bd0053f91a508fbf5a8')"
56+
]
57+
}
58+
}

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserWebViewClientTest.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import androidx.test.filters.SdkSuppress
2424
import androidx.test.platform.app.InstrumentationRegistry
2525
import com.duckduckgo.app.CoroutineTestRule
2626
import com.duckduckgo.app.browser.certificates.rootstore.TrustedCertificateStore
27+
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
2728
import com.duckduckgo.app.browser.logindetection.DOMLoginDetector
2829
import com.duckduckgo.app.browser.logindetection.WebNavigationEvent
2930
import com.duckduckgo.app.browser.model.BasicAuthenticationRequest
@@ -57,12 +58,14 @@ class BrowserWebViewClientTest {
5758
private val dosDetector: DosDetector = DosDetector()
5859
private val globalPrivacyControl: GlobalPrivacyControl = mock()
5960
private val trustedCertificateStore: TrustedCertificateStore = mock()
61+
private val webViewHttpAuthStore: WebViewHttpAuthStore = mock()
6062

6163
@UiThreadTest
6264
@Before
6365
fun setup() {
6466
webView = TestWebView(InstrumentationRegistry.getInstrumentation().targetContext)
6567
testee = BrowserWebViewClient(
68+
webViewHttpAuthStore,
6669
trustedCertificateStore,
6770
requestRewriter,
6871
specialUrlDetector,

app/src/androidTest/java/com/duckduckgo/app/browser/WebViewDataManagerTest.kt

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import android.webkit.WebStorage
2121
import android.webkit.WebView
2222
import android.webkit.WebViewDatabase
2323
import androidx.test.platform.app.InstrumentationRegistry
24+
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
2425
import com.duckduckgo.app.browser.session.WebViewSessionInMemoryStorage
2526
import com.duckduckgo.app.fire.DuckDuckGoCookieManager
2627
import com.duckduckgo.app.global.file.FileDeleter
@@ -40,13 +41,14 @@ class WebViewDataManagerTest {
4041
private val context = InstrumentationRegistry.getInstrumentation().targetContext
4142
private val mockFileDeleter: FileDeleter = mock()
4243
private val mockWebViewDatabase: WebViewDatabase = mock()
43-
private val testee = WebViewDataManager(context, WebViewSessionInMemoryStorage(), mockCookieManager, mockFileDeleter)
44+
private val mockWebViewHttpAuthStore: WebViewHttpAuthStore = mock()
45+
private val testee = WebViewDataManager(context, WebViewSessionInMemoryStorage(), mockCookieManager, mockFileDeleter, mockWebViewHttpAuthStore)
4446

4547
@Test
4648
fun whenDataClearedThenWebViewHistoryCleared() = runBlocking<Unit> {
4749
withContext(Dispatchers.Main) {
4850
val webView = TestWebView(context)
49-
testee.clearData(webView, mockStorage, mockWebViewDatabase)
51+
testee.clearData(webView, mockStorage)
5052
assertTrue(webView.historyCleared)
5153
}
5254
}
@@ -55,7 +57,7 @@ class WebViewDataManagerTest {
5557
fun whenDataClearedThenWebViewCacheCleared() = runBlocking<Unit> {
5658
withContext(Dispatchers.Main) {
5759
val webView = TestWebView(context)
58-
testee.clearData(webView, mockStorage, mockWebViewDatabase)
60+
testee.clearData(webView, mockStorage)
5961
assertTrue(webView.cacheCleared)
6062
}
6163
}
@@ -64,7 +66,7 @@ class WebViewDataManagerTest {
6466
fun whenDataClearedThenWebViewFormDataCleared() = runBlocking<Unit> {
6567
withContext(Dispatchers.Main) {
6668
val webView = TestWebView(context)
67-
testee.clearData(webView, mockStorage, mockWebViewDatabase)
69+
testee.clearData(webView, mockStorage)
6870
assertTrue(webView.clearedFormData)
6971
}
7072
}
@@ -73,7 +75,7 @@ class WebViewDataManagerTest {
7375
fun whenDataClearedThenWebViewWebStorageCleared() = runBlocking<Unit> {
7476
withContext(Dispatchers.Main) {
7577
val webView = TestWebView(context)
76-
testee.clearData(webView, mockStorage, mockWebViewDatabase)
78+
testee.clearData(webView, mockStorage)
7779
verify(mockStorage).deleteAllData()
7880
}
7981
}
@@ -82,16 +84,16 @@ class WebViewDataManagerTest {
8284
fun whenDataClearedThenWebViewAuthCredentialsCleared() = runBlocking<Unit> {
8385
withContext(Dispatchers.Main) {
8486
val webView = TestWebView(context)
85-
testee.clearData(webView, mockStorage, mockWebViewDatabase)
86-
verify(mockWebViewDatabase).clearHttpAuthUsernamePassword()
87+
testee.clearData(webView, mockStorage)
88+
verify(mockWebViewHttpAuthStore).clearHttpAuthUsernamePassword(webView)
8789
}
8890
}
8991

9092
@Test
9193
fun whenDataClearedThenWebViewCookiesRemoved() = runBlocking<Unit> {
9294
withContext(Dispatchers.Main) {
9395
val webView = TestWebView(context)
94-
testee.clearData(webView, mockStorage, mockWebViewDatabase)
96+
testee.clearData(webView, mockStorage)
9597
verify(mockCookieManager).removeExternalCookies()
9698
}
9799
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright (c) 2021 DuckDuckGo
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.duckduckgo.app.browser.httpauth
18+
19+
import android.webkit.WebView
20+
import com.duckduckgo.app.CoroutineTestRule
21+
import com.duckduckgo.app.browser.httpauth.db.HttpAuthDao
22+
import com.duckduckgo.app.browser.httpauth.db.HttpAuthEntity
23+
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
24+
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteEntity
25+
import com.nhaarman.mockitokotlin2.mock
26+
import com.nhaarman.mockitokotlin2.verify
27+
import com.nhaarman.mockitokotlin2.whenever
28+
import kotlinx.coroutines.ExperimentalCoroutinesApi
29+
import org.junit.Assert.assertEquals
30+
import org.junit.Rule
31+
import org.junit.Test
32+
33+
@ExperimentalCoroutinesApi
34+
class WebViewHttpAuthStoreTest {
35+
36+
@get:Rule
37+
@Suppress("unused")
38+
val coroutineRule = CoroutineTestRule()
39+
40+
private val fireproofWebsiteDao: FireproofWebsiteDao = mock()
41+
private val httpAuthDao: HttpAuthDao = mock()
42+
private val webView: WebView = mock()
43+
44+
private val webViewHttpAuthStore = RealWebViewHttpAuthStore(coroutineRule.testDispatcherProvider, fireproofWebsiteDao, httpAuthDao)
45+
46+
@Test
47+
fun whenSetHttpAuthUsernamePasswordThenInsertHttpAuthEntity() {
48+
webViewHttpAuthStore.setHttpAuthUsernamePassword(
49+
webView = webView,
50+
host = "host",
51+
realm = "realm",
52+
username = "name",
53+
password = "pass",
54+
)
55+
56+
verify(httpAuthDao).insert(
57+
HttpAuthEntity(
58+
host = "host",
59+
realm = "realm",
60+
username = "name",
61+
password = "pass"
62+
)
63+
)
64+
}
65+
66+
@Test
67+
fun whenGetHttpAuthUsernamePasswordThenReturnWebViewHttpAuthCredentials() {
68+
whenever(httpAuthDao.getAuthCredentials("host", "realm"))
69+
.thenReturn(HttpAuthEntity(1, "host", "realm", "name", "pass"))
70+
val credentials = webViewHttpAuthStore.getHttpAuthUsernamePassword(webView, "host", "realm")
71+
72+
assertEquals(WebViewHttpAuthCredentials("name", "pass"), credentials)
73+
}
74+
75+
@Test
76+
fun whenClearHttpAuthUsernamePasswordThenDeleteAllCredentialsExceptExclusions() {
77+
whenever(fireproofWebsiteDao.fireproofWebsitesSync())
78+
.thenReturn(listOf(FireproofWebsiteEntity("http://fireproofed.me")))
79+
80+
webViewHttpAuthStore.clearHttpAuthUsernamePassword(webView)
81+
82+
verify(httpAuthDao).deleteAll(listOf("http://fireproofed.me"))
83+
}
84+
}

app/src/androidTest/java/com/duckduckgo/app/di/StubDatabaseModule.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@ package com.duckduckgo.app.di
1818

1919
import android.content.Context
2020
import androidx.room.Room
21+
import com.duckduckgo.app.browser.httpauth.RealWebViewHttpAuthStore
22+
import com.duckduckgo.app.browser.httpauth.WebViewHttpAuthStore
23+
import com.duckduckgo.app.browser.httpauth.db.HttpAuthDatabase
24+
import com.duckduckgo.app.fire.fireproofwebsite.data.FireproofWebsiteDao
25+
import com.duckduckgo.app.global.DefaultDispatcherProvider
2126
import com.duckduckgo.app.global.db.AppDatabase
2227
import dagger.Module
2328
import dagger.Provides
@@ -33,4 +38,17 @@ class StubDatabaseModule {
3338
.allowMainThreadQueries()
3439
.build()
3540
}
41+
42+
@Provides
43+
@Singleton
44+
fun provideWebViewHttpAuthStore(
45+
context: Context,
46+
fireproofWebsiteDao: FireproofWebsiteDao
47+
): WebViewHttpAuthStore {
48+
val db = Room.inMemoryDatabaseBuilder(context, HttpAuthDatabase::class.java)
49+
.allowMainThreadQueries()
50+
.build()
51+
52+
return RealWebViewHttpAuthStore(DefaultDispatcherProvider(), fireproofWebsiteDao, db.httpAuthDao())
53+
}
3654
}

app/src/androidTest/java/com/duckduckgo/app/global/view/ClearPersonalDataActionTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class ClearPersonalDataActionTest {
8080
@Test
8181
fun whenClearCalledThenDataManagerClearsData() = runBlocking<Unit> {
8282
testee.clearTabsAndAllDataAsync(appInForeground = false, shouldFireDataClearPixel = false)
83-
verify(mockDataManager).clearData(any(), any(), any())
83+
verify(mockDataManager).clearData(any(), any())
8484
}
8585

8686
@Test

0 commit comments

Comments
 (0)