Skip to content

Commit b3918b5

Browse files
committed
Merge branch 'hotfix5.216.2'
2 parents 8bf30a2 + e4d8ffe commit b3918b5

File tree

107 files changed

+3595
-4275
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

107 files changed

+3595
-4275
lines changed

.github/workflows/e2e-nightly-autofill.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
api-key: ${{ secrets.MOBILE_DEV_API_KEY }}
6161
name: ${{ github.sha }}
6262
app-file: apk/release.apk
63-
android-api-level: 33
63+
android-api-level: 30
6464
workspace: .maestro
6565
include-tags: autofillNoAuthTests
6666

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

+55-7
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,6 @@ import com.duckduckgo.app.usage.search.SearchCountDao
174174
import com.duckduckgo.app.widget.ui.WidgetCapabilities
175175
import com.duckduckgo.appbuildconfig.api.AppBuildConfig
176176
import com.duckduckgo.autofill.api.AutofillCapabilityChecker
177-
import com.duckduckgo.autofill.api.AutofillWebMessageRequest
178177
import com.duckduckgo.autofill.api.domain.app.LoginCredentials
179178
import com.duckduckgo.autofill.api.email.EmailManager
180179
import com.duckduckgo.autofill.api.passwordgeneration.AutomaticSavedLoginsMonitor
@@ -3790,22 +3789,59 @@ class BrowserTabViewModelTest {
37903789
assertTrue(browserViewState().isEmailSignedIn)
37913790
}
37923791

3792+
@Test
3793+
fun whenEmailSignOutEventThenEmailSignEventCommandSent() = runTest {
3794+
emailStateFlow.emit(true)
3795+
emailStateFlow.emit(false)
3796+
3797+
assertCommandIssuedTimes<Command.EmailSignEvent>(2)
3798+
}
3799+
3800+
@Test
3801+
fun whenEmailIsSignedInThenEmailSignEventCommandSent() = runTest {
3802+
emailStateFlow.emit(true)
3803+
3804+
assertCommandIssued<Command.EmailSignEvent>()
3805+
}
3806+
3807+
@Test
3808+
fun whenConsumeAliasThenInjectAddressCommandSent() {
3809+
whenever(mockEmailManager.getAlias()).thenReturn("alias")
3810+
3811+
testee.usePrivateDuckAddress("", "alias")
3812+
3813+
assertCommandIssued<Command.InjectEmailAddress> {
3814+
assertEquals("alias", this.duckAddress)
3815+
}
3816+
}
3817+
3818+
@Test
3819+
fun whenUseAddressThenInjectAddressCommandSent() {
3820+
whenever(mockEmailManager.getEmailAddress()).thenReturn("address")
3821+
3822+
testee.usePersonalDuckAddress("", "address")
3823+
3824+
assertCommandIssued<Command.InjectEmailAddress> {
3825+
assertEquals("address", this.duckAddress)
3826+
}
3827+
}
3828+
37933829
@Test
37943830
fun whenShowEmailTooltipIfAddressExistsThenShowEmailTooltipCommandSent() {
37953831
whenever(mockEmailManager.getEmailAddress()).thenReturn("address")
37963832

3797-
testee.showEmailProtectionChooseEmailPrompt(urlRequest())
3833+
testee.showEmailProtectionChooseEmailPrompt()
37983834

37993835
assertCommandIssued<Command.ShowEmailProtectionChooseEmailPrompt> {
3800-
assertEquals("address", this.duckAddress)
3836+
assertEquals("address", this.address)
38013837
}
38023838
}
38033839

38043840
@Test
38053841
fun whenShowEmailTooltipIfAddressDoesNotExistThenCommandNotSent() {
38063842
whenever(mockEmailManager.getEmailAddress()).thenReturn(null)
38073843

3808-
testee.showEmailProtectionChooseEmailPrompt(urlRequest())
3844+
testee.showEmailProtectionChooseEmailPrompt()
38093845

38103846
assertCommandNotIssued<Command.ShowEmailProtectionChooseEmailPrompt>()
38113847
}
@@ -4406,6 +4442,16 @@ class BrowserTabViewModelTest {
44064442
assertShowHistoryCommandSent(expectedStackSize = 10)
44074443
}
44084444

4445+
@Test
4446+
fun whenReturnNoCredentialsWithPageThenEmitCancelIncomingAutofillRequestCommand() = runTest {
4447+
val url = "originalurl.com"
4448+
testee.returnNoCredentialsWithPage(url)
4449+
4450+
assertCommandIssued<Command.CancelIncomingAutofillRequest> {
4451+
assertEquals(url, this.url)
4452+
}
4453+
}
4454+
44094455
@Test
44104456
fun whenOnAutoconsentResultReceivedThenSiteUpdated() {
44114457
updateUrl("http://www.example.com/", "http://twitter.com/explore", true)
@@ -5992,8 +6038,6 @@ class BrowserTabViewModelTest {
59926038
}
59936039
}
59946040

5995-
private fun urlRequest() = AutofillWebMessageRequest("", "", "")
5996-
59976041
private fun givenLoginDetected(domain: String) = LoginDetected(authLoginDomain = "", forwardedToDomain = domain)
59986042

59996043
private fun givenCurrentSite(domain: String): Site {
@@ -6146,6 +6190,10 @@ class BrowserTabViewModelTest {
61466190
fun anyUri(): Uri = any()
61476191

61486192
class FakeCapabilityChecker(var enabled: Boolean) : AutofillCapabilityChecker {
6149-
override suspend fun canAccessCredentialManagementScreen(): Boolean = enabled
6193+
override suspend fun isAutofillEnabledByConfiguration(url: String) = enabled
6194+
override suspend fun canInjectCredentialsToWebView(url: String) = enabled
6195+
override suspend fun canSaveCredentialsFromWebView(url: String) = enabled
6196+
override suspend fun canGeneratePasswordFromWebView(url: String) = enabled
6197+
override suspend fun canAccessCredentialManagementScreen() = enabled
61506198
}
61516199
}

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

+10
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import com.duckduckgo.app.pixels.AppPixelName
6161
import com.duckduckgo.app.statistics.pixels.Pixel
6262
import com.duckduckgo.app.statistics.pixels.Pixel.PixelParameter.LOADING_BAR_EXPERIMENT
6363
import com.duckduckgo.autoconsent.api.Autoconsent
64+
import com.duckduckgo.autofill.api.BrowserAutofill
6465
import com.duckduckgo.autofill.api.InternalTestUserChecker
6566
import com.duckduckgo.browser.api.JsInjectorPlugin
6667
import com.duckduckgo.browser.api.WebViewVersionProvider
@@ -117,6 +118,7 @@ class BrowserWebViewClientTest {
117118
private val trustedCertificateStore: TrustedCertificateStore = mock()
118119
private val webViewHttpAuthStore: WebViewHttpAuthStore = mock()
119120
private val thirdPartyCookieManager: ThirdPartyCookieManager = mock()
121+
private val browserAutofillConfigurator: BrowserAutofill.Configurator = mock()
120122
private val webResourceRequest: WebResourceRequest = mock()
121123
private val webResourceError: WebResourceError = mock()
122124
private val ampLinks: AmpLinks = mock()
@@ -154,6 +156,7 @@ class BrowserWebViewClientTest {
154156
thirdPartyCookieManager,
155157
TestScope(),
156158
coroutinesTestRule.testDispatcherProvider,
159+
browserAutofillConfigurator,
157160
ampLinks,
158161
printInjector,
159162
internalTestUserChecker,
@@ -367,6 +370,13 @@ class BrowserWebViewClientTest {
367370
verify(cookieManager).flush()
368371
}
369372

373+
@UiThreadTest
374+
@Test
375+
fun whenOnPageStartedCalledThenInjectEmailAutofillJsCalled() {
376+
testee.onPageStarted(webView, null, null)
377+
verify(browserAutofillConfigurator).configureAutofillForCurrentPage(webView, null)
378+
}
379+
370380
@UiThreadTest
371381
@Test
372382
fun whenShouldOverrideThrowsExceptionThenRecordException() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
* Copyright (c) 2022 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.email
18+
19+
import android.annotation.SuppressLint
20+
import android.webkit.WebView
21+
import androidx.test.annotation.UiThreadTest
22+
import androidx.test.filters.SdkSuppress
23+
import androidx.test.platform.app.InstrumentationRegistry
24+
import com.duckduckgo.app.autofill.DefaultEmailProtectionJavascriptInjector
25+
import com.duckduckgo.app.autofill.EmailProtectionJavascriptInjector
26+
import com.duckduckgo.app.browser.DuckDuckGoUrlDetectorImpl
27+
import com.duckduckgo.app.browser.R
28+
import com.duckduckgo.autofill.api.Autofill
29+
import com.duckduckgo.autofill.api.AutofillFeature
30+
import com.duckduckgo.autofill.api.email.EmailManager
31+
import com.duckduckgo.common.utils.DispatcherProvider
32+
import com.duckduckgo.feature.toggles.api.FakeFeatureToggleFactory
33+
import com.duckduckgo.feature.toggles.api.Toggle
34+
import java.io.BufferedReader
35+
import org.junit.Before
36+
import org.junit.Test
37+
import org.mockito.kotlin.*
38+
39+
class EmailInjectorJsTest {
40+
41+
private val mockEmailManager: EmailManager = mock()
42+
private val mockDispatcherProvider: DispatcherProvider = mock()
43+
private val autofillFeature = FakeFeatureToggleFactory.create(AutofillFeature::class.java)
44+
private val mockAutofill: Autofill = mock()
45+
private val javascriptInjector: EmailProtectionJavascriptInjector = DefaultEmailProtectionJavascriptInjector()
46+
47+
lateinit var testee: EmailInjectorJs
48+
49+
@Before
50+
fun setup() {
51+
testee =
52+
EmailInjectorJs(
53+
mockEmailManager,
54+
DuckDuckGoUrlDetectorImpl(),
55+
mockDispatcherProvider,
56+
autofillFeature,
57+
javascriptInjector,
58+
mockAutofill,
59+
)
60+
whenever(mockAutofill.isAnException(any())).thenReturn(false)
61+
}
62+
63+
@SuppressLint("DenyListedApi")
64+
@UiThreadTest
65+
@Test
66+
@SdkSuppress(minSdkVersion = 24)
67+
fun whenInjectAddressThenInjectJsCodeReplacingTheAlias() {
68+
val address = "address"
69+
val jsToEvaluate = getAliasJsToEvaluate().replace("%s", address)
70+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
71+
autofillFeature.self().setRawStoredState(Toggle.State(enable = true))
72+
73+
testee.injectAddressInEmailField(webView, address, "https://example.com")
74+
75+
verify(webView).evaluateJavascript(jsToEvaluate, null)
76+
}
77+
78+
@SuppressLint("DenyListedApi")
79+
@UiThreadTest
80+
@Test
81+
@SdkSuppress(minSdkVersion = 24)
82+
fun whenInjectAddressAndFeatureIsDisabledThenJsCodeNotInjected() {
83+
autofillFeature.self().setRawStoredState(Toggle.State(enable = true))
84+
85+
val address = "address"
86+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
87+
88+
testee.injectAddressInEmailField(webView, address, "https://example.com")
89+
90+
verify(webView, never()).evaluateJavascript(any(), any())
91+
}
92+
93+
@UiThreadTest
94+
@Test
95+
@SdkSuppress(minSdkVersion = 24)
96+
fun whenInjectAddressAndUrlIsAnExceptionThenJsCodeNotInjected() {
97+
whenever(mockAutofill.isAnException(any())).thenReturn(true)
98+
99+
val address = "address"
100+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
101+
102+
testee.injectAddressInEmailField(webView, address, "https://example.com")
103+
104+
verify(webView, never()).evaluateJavascript(any(), any())
105+
}
106+
107+
@UiThreadTest
108+
@Test
109+
@SdkSuppress(minSdkVersion = 24)
110+
fun whenNotifyWebAppSignEventAndUrlIsNotFromDuckDuckGoAndEmailIsSignedInThenDoNotEvaluateJsCode() {
111+
whenever(mockEmailManager.isSignedIn()).thenReturn(true)
112+
val jsToEvaluate = getNotifySignOutJsToEvaluate()
113+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
114+
115+
testee.notifyWebAppSignEvent(webView, "https://example.com")
116+
117+
verify(webView, never()).evaluateJavascript(jsToEvaluate, null)
118+
}
119+
120+
@UiThreadTest
121+
@Test
122+
@SdkSuppress(minSdkVersion = 24)
123+
fun whenNotifyWebAppSignEventAndUrlIsNotFromDuckDuckGoAndEmailIsNotSignedInThenDoNotEvaluateJsCode() {
124+
whenever(mockEmailManager.isSignedIn()).thenReturn(false)
125+
val jsToEvaluate = getNotifySignOutJsToEvaluate()
126+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
127+
128+
testee.notifyWebAppSignEvent(webView, "https://example.com")
129+
130+
verify(webView, never()).evaluateJavascript(jsToEvaluate, null)
131+
}
132+
133+
@SuppressLint("DenyListedApi")
134+
@UiThreadTest
135+
@Test
136+
@SdkSuppress(minSdkVersion = 24)
137+
fun whenNotifyWebAppSignEventAndUrlIsFromDuckDuckGoAndFeatureIsDisabledAndEmailIsNotSignedInThenDoNotEvaluateJsCode() {
138+
whenever(mockEmailManager.isSignedIn()).thenReturn(false)
139+
autofillFeature.self().setRawStoredState(Toggle.State(enable = false))
140+
141+
val jsToEvaluate = getNotifySignOutJsToEvaluate()
142+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
143+
144+
testee.notifyWebAppSignEvent(webView, "https://duckduckgo.com/email")
145+
146+
verify(webView, never()).evaluateJavascript(jsToEvaluate, null)
147+
}
148+
149+
@SuppressLint("DenyListedApi")
150+
@UiThreadTest
151+
@Test
152+
@SdkSuppress(minSdkVersion = 24)
153+
fun whenNotifyWebAppSignEventAndUrlIsFromDuckDuckGoAndFeatureIsEnabledAndEmailIsNotSignedInThenEvaluateJsCode() {
154+
whenever(mockEmailManager.isSignedIn()).thenReturn(false)
155+
autofillFeature.self().setRawStoredState(Toggle.State(enable = true))
156+
157+
val jsToEvaluate = getNotifySignOutJsToEvaluate()
158+
val webView = spy(WebView(InstrumentationRegistry.getInstrumentation().targetContext))
159+
160+
testee.notifyWebAppSignEvent(webView, "https://duckduckgo.com/email")
161+
162+
verify(webView).evaluateJavascript(jsToEvaluate, null)
163+
}
164+
165+
private fun getAliasJsToEvaluate(): String {
166+
val js = InstrumentationRegistry.getInstrumentation().targetContext.resources.openRawResource(R.raw.inject_alias)
167+
.bufferedReader()
168+
.use { it.readText() }
169+
return "javascript:$js"
170+
}
171+
172+
private fun getNotifySignOutJsToEvaluate(): String {
173+
val js =
174+
InstrumentationRegistry.getInstrumentation().targetContext.resources.openRawResource(R.raw.signout_autofill)
175+
.bufferedReader()
176+
.use { it.readText() }
177+
return "javascript:$js"
178+
}
179+
180+
private fun readResource(resourceName: String): BufferedReader? {
181+
return javaClass.classLoader?.getResource(resourceName)?.openStream()?.bufferedReader()
182+
}
183+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) 2022 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.autofill
18+
19+
import android.content.Context
20+
import com.duckduckgo.app.browser.R
21+
import com.duckduckgo.di.scopes.AppScope
22+
import com.squareup.anvil.annotations.ContributesBinding
23+
import dagger.SingleInstanceIn
24+
import javax.inject.Inject
25+
26+
@SingleInstanceIn(AppScope::class)
27+
@ContributesBinding(AppScope::class)
28+
class DefaultEmailProtectionJavascriptInjector @Inject constructor() : EmailProtectionJavascriptInjector {
29+
private lateinit var aliasFunctions: String
30+
private lateinit var signOutFunctions: String
31+
32+
override fun getAliasFunctions(
33+
context: Context,
34+
alias: String?,
35+
): String {
36+
if (!this::aliasFunctions.isInitialized) {
37+
aliasFunctions = context.resources.openRawResource(R.raw.inject_alias).bufferedReader().use { it.readText() }
38+
}
39+
return aliasFunctions.replace("%s", alias.orEmpty())
40+
}
41+
42+
override fun getSignOutFunctions(
43+
context: Context,
44+
): String {
45+
if (!this::signOutFunctions.isInitialized) {
46+
signOutFunctions = context.resources.openRawResource(R.raw.signout_autofill).bufferedReader().use { it.readText() }
47+
}
48+
return signOutFunctions
49+
}
50+
}

0 commit comments

Comments
 (0)