Commit 2f998009 authored by Tiger Oakes's avatar Tiger Oakes
Browse files

Closes #3707 - Add SecureWindowFeature

parent 7b25cd70
......@@ -23,15 +23,19 @@ android {
}
dependencies {
implementation project(':browser-session')
implementation project(':browser-state')
implementation project(':support-ktx')
implementation Dependencies.androidx_core_ktx
implementation Dependencies.kotlin_coroutines
implementation Dependencies.kotlin_stdlib
testImplementation project(':support-test')
testImplementation Dependencies.androidx_test_core
testImplementation Dependencies.androidx_test_junit
testImplementation Dependencies.testing_coroutines
testImplementation Dependencies.testing_robolectric
testImplementation Dependencies.testing_mockito
}
......
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.feature.privatemode
internal class Placeholder
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.feature.privatemode.feature
import android.view.Window
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull
import mozilla.components.browser.state.selector.findCustomTabOrSelectedTab
import mozilla.components.browser.state.state.SessionState
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifChanged
/**
* Prevents screenshots and screen recordings in private tabs.
*
* @param isSecure Returns true if the session should have [FLAG_SECURE] set.
* Can be overriden to customize when the secure flag is set.
*/
class SecureWindowFeature(
private val window: Window,
private val store: BrowserStore,
private val customTabId: String? = null,
private val isSecure: (SessionState) -> Boolean = { it.content.private }
) : LifecycleAwareFeature {
private var scope: CoroutineScope? = null
@ExperimentalCoroutinesApi
override fun start() {
scope = store.flowScoped { flow ->
flow.mapNotNull { state -> state.findCustomTabOrSelectedTab(customTabId) }
.map { isSecure(it) }
.ifChanged()
.collect { isSecure ->
if (isSecure) {
window.addFlags(FLAG_SECURE)
} else {
window.clearFlags(FLAG_SECURE)
}
}
}
}
override fun stop() {
scope?.cancel()
}
}
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.feature.privatemode
class PlaceholderTest
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package mozilla.components.feature.privatemode.feature
import android.view.Window
import android.view.WindowManager.LayoutParams.FLAG_SECURE
import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import mozilla.components.browser.state.state.BrowserState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.support.test.mock
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class SecureWindowFeatureTest {
private val testDispatcher = TestCoroutineDispatcher()
private lateinit var window: Window
private val tabId = "test-tab"
@Before
fun setup() {
Dispatchers.setMain(testDispatcher)
window = mock()
}
@After
fun tearDown() {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
@Test
fun `no-op if no sessions`() {
val store = BrowserStore(BrowserState(tabs = emptyList()))
val feature = SecureWindowFeature(window, store)
feature.start()
verify(window, never()).addFlags(FLAG_SECURE)
verify(window, never()).clearFlags(FLAG_SECURE)
}
@Test
fun `add flags to private session`() {
val store = BrowserStore(
BrowserState(
tabs = listOf(
createTab("https://www.mozilla.org", id = tabId, private = true)
),
selectedTabId = tabId
)
)
val feature = SecureWindowFeature(window, store)
feature.start()
verify(window).addFlags(FLAG_SECURE)
}
@Test
fun `remove flags from normal session`() {
val store = BrowserStore(
BrowserState(
tabs = listOf(
createTab("https://www.mozilla.org", id = tabId, private = false)
),
selectedTabId = tabId
)
)
val feature = SecureWindowFeature(window, store)
feature.start()
verify(window).clearFlags(FLAG_SECURE)
}
}
......@@ -14,7 +14,7 @@ permalink: /changelog/
* **browser-toolbar**
* ⚠️ **This is a breaking change**: Refactored the internals to use `ConstraintLayout`. As part of this change the public API was simplified and unused methods/properties have been removed.
* **feature-accounts**
* Add new `FxaPushSupportFeature` for some underlying support when connecting push and fxa accounts together.
......@@ -25,6 +25,10 @@ permalink: /changelog/
* The Rust implementation of the Glean SDK is now being used.
* ⚠️ **This is a breaking change**: the `GleanDebugActivity` is no longer exposed from service-glean. Users need to use the one in `mozilla.telemetry.glean.debug.GleanDebugActivity` from the `adb` command line.
* **feature-privatemode**
* Added new feature for private browsing mode.
* Added `SecureWindowFeature` to prevent screenshots in private browsing mode.
# 18.0.0
* [Commits](https://github.com/mozilla-mobile/android-components/compare/v17.0.0...v18.0.0)
......@@ -158,7 +162,7 @@ permalink: /changelog/
* Behavior change: In a collection List<TabEntity> is now ordered descending by creation date (newest tab in a collection on top)
* **feature-session**, **engine-gecko-nightly** and **engine-gecko-beta**
* Added api to manage the tracking protection exception list, any session added to the list will be ignored and the the current tracking policy will not be applied.
```kotlin
val useCase = TrackingProtectionUseCases(sessionManager,engine)
......@@ -215,7 +219,7 @@ permalink: /changelog/
* **feature-session**, **engine-gecko-nightly** and **engine-gecko-beta**
* Added a way to exposes the same amount of trackers as Firefox desktop has in it tracking protection panel via TrackingProtectionUseCases.
```kotlin
val useCase = TrackingProtectionUseCases(sessionManager,engine)
useCase.fetchTrackingLogs(
......
......@@ -91,6 +91,7 @@ dependencies {
implementation project(':feature-toolbar')
implementation project(':feature-tabs')
implementation project(':feature-prompts')
implementation project(':feature-privatemode')
implementation project(':feature-pwa')
implementation project(':feature-findinpage')
implementation project(':feature-sitepermissions')
......
......@@ -19,6 +19,7 @@ import mozilla.components.feature.contextmenu.ContextMenuCandidate
import mozilla.components.feature.contextmenu.ContextMenuFeature
import mozilla.components.feature.downloads.DownloadsFeature
import mozilla.components.feature.downloads.manager.FetchDownloadManager
import mozilla.components.feature.privatemode.feature.SecureWindowFeature
import mozilla.components.feature.prompts.PromptFeature
import mozilla.components.feature.session.CoordinateScrollingFeature
import mozilla.components.feature.session.SessionFeature
......@@ -164,11 +165,18 @@ abstract class BaseBrowserFragment : Fragment(), BackHandler {
owner = this,
view = layout)
val secureWindowFeature = SecureWindowFeature(
window = requireActivity().window,
store = components.store,
customTabId = sessionId
)
// Observe the lifecycle for supported features
lifecycle.addObservers(
scrollFeature,
contextMenuFeature,
menuUpdaterFeature
menuUpdaterFeature,
secureWindowFeature
)
appLinksFeature.set(
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment