Skip to content

Commit

Permalink
Integrate AppTP with privacy remote config (#1832)
Browse files Browse the repository at this point in the history
Task/Issue URL: https://app.asana.com/0/72649045549333/1202005414608950/f

### Description
_NOTE: originally stacked PR onto #1830 -> #1829_ 

Integrates AppTP with privacy remote config

### Steps to test this PR

_Test internal builds_
- [x] fresh install from INTERNAL debug builds
- [x] launch the app
- [x] verify the `apptp_remote_config.db` contains a table dubbed `vpn_config_toggles`
- [x] verify the `vpn_config_toggles` table contains `ipv6Support`, `privateDnsSupport`, `networkSwitchHandling` and `badHealthMitigation` entries
- [x] verify all entries are false (`0`) for both `enabled` and `isManualOverride` columns except for `badHealthMitigation` that should have `enabled` set to true (`1`)
- [x] go to Settings -> AppTP dev settings
- [x] verify all toggles are disabled
- [x] go to Settings -> App Tracking Protection and enable AppTP
- [x] go to Settings -> AppTP dev settings
- [x] verify all toggles are in state enabled
- [x] verify only `VPN debug logging`, `Bad health monitoring` and `Bad health mitigation action` toggles are ON, remaining ones should be OFF
- [x] filter logcat by `Dropping ipv6`
- [x] open instagram and verify logs appear (dropping IPv6 packets) - you may need to in cellular if you don't see logs on WIFI
- [x] close instagram
- [x] go to Settings -> AppTP dev settings and toggle IPv6 support ON
- [x] open instagram and verify no logs appear
- [x] go to Settings -> AppTP dev settings and toggle IPv6 support OFF
- [x] filter logcat by `private dns`
- [x] go to Android settings and search for private DNS
- [x] set `Private DNS provider hostname` to `one.one.one.one`
- [x] verify `Private DNS support is disabled...skip` appear
- [x] set Private DNS to off
- [x] verify `Private DNS support is disabled...skip` appear
- [x] go to Settings -> AppTP dev settings and toggle `Private DNS support` ON
- [x] set `Private DNS provider hostname` to `one.one.one.one`
- [x] verify `Setting private DNS: ...` appear and VPN is restarted (look at the key icon in the status bar)
- [x] set Private DNS to off
- [x] verify `Setting private DNS: ...` DO NOT appear and VPN is restarted (look at the key icon in the status bar)
- [x] go to Settings -> AppTP dev settings and toggle `IPv6 Support` and `private DNS support` ON
- [x] In AS, open `privacy_config.json` file and set the `version` to `1648641758000` to force reload the remote settings on app launch
- [x] Filter logcat by `AppTpFeatureConfigImpl`
- [x] rebuild and install the app
- [x] launch the app
- [x] verify `Skip setEnabled...` appear for both `Ipv6Support` and `PrivateDnsSupport`
- [x] go to Settings -> AppTP dev settings
- [x] verify both IPv6 and privacy support toggles are still ON
- [x] verify opening instagram does not log any `Dropping ipv6...` message in logcat

_Test PLAY build_
- [x] Modify `AppTPHealthMonitor:simulateHealthStatusIfEnabled` method, so that the first `if` condition is `if (!appBuildConfig.isDebug)`
- [x] build and fresn install from PLAY debug build
- [x] Enable AppTp - grab invice code from [here](https://app.asana.com/0/0/1201184915504678/f)
- [x] Verify in logcat ipv6 packets are dropped when opening instagram
- [x] Verify in logcat `Private DNS support is disabled...skip` appears when trying to set private DNS from Android settings
- [x] Go to Settings -> App Tracking Protection screen -> overflo menu -> Diagnostics
= [ ] filter logcat by `AppBadHealthStateHandler`
- [x] scroll down and click on `BAD HEALTH` button
- [x] verify the `AppBadHealthStateHandler: Restarting the VPN....` message appears in logcat after a while
- [x] Click in `GOOD HEALTH` and wait for `no alerts` message
- [x] Click on `CRITICAL HEALTH`
- [x] verify that after a while `Restarting the VPN...` meessage appears and the diagnostics screen is killed (becauase we killed that process)

_Test app update_
- [ ] install from develop branch the INTERNAL build
- [ ] launch app and enable AppTP
- [ ] verify there is no `apptp_remote_config.db` database
- [ ] go to Settings -> AppTP settings
- [ ] verify `VPN debug logging` and `Bad health monitoring` and `Bad health mitigation` toggles are ON
- [ ] verify the rest of toggles are OFF
- [ ] click on `View diagnostics Data`
- [ ] scroll down and click on `CRITICAL HEALTH`
- [ ] verify the current activity is killed (it can take a while) - this is because the VPN process is restarted
- [ ] verify AppTP is enabled
- [ ] update the app from this branch (INTERNAL build)
- [ ] verify the `apptp_remote_config.db` exists and it has an EMPTY `vpn_config_toggles` table
- [ ] go to Settings -> AppTP settings
- [ ] verify `VPN debug logging` and `Bad health monitoring` and `Bad health mitigation` toggles are ON
- [ ] click on `View diagnostics Data`
- [ ] scroll down and click on `CRITICAL HEALTH`
- [ ] verify the current activity is killed (it can take a while) - this is because the VPN process is restarted
- [ ] verify AppTP is enabled
- [ ] go to Settings -> AppTP settings and enable `IPv6 support`
- [ ] verify the `vpn_config_toggles` db table contains the `ipv6Support` entry with `enabled` and `isManuallyOverride` columns set to `1`
- [ ] force kill the app and relaunch it
- [ ] go to Settings -> AppTP settings
- [ ] verify `ipv6 support` toggle is still ON
- [ ] verify the `vpn_config_toggles` db table contains the `ipv6Support` entry with `enabled` and `isManuallyOverride` columns set to `1`

_Test remote config updates_
- [ ] install from this branch the INTERNAL build
- [ ] open the app and enable AppTP
- [ ] go to Settings -> AppTP dev settings
- [ ] verify all toggles under `CONFIG` section are OFF
- [ ] verify the `vpn_config_toggles` table in the `apptp_remote_config.db` also has entries for all toggles (only badHealthMitigation` entry is enabled)
- [ ] use Charles to update the remote config file and update the ipv6 config to enabled and update the version
- [ ] kill and restart the app
- [ ] go to Settings -> AppTP dev settings
- [ ] verify the ipv6 toggle is ON
- [ ] verify the `vpn_config_toggles` table in the `apptp_remote_config.db` also has the ipv6 config as enabled and with `isManuallyOverride` set to `0`
  • Loading branch information
aitorvs authored Mar 31, 2022
1 parent 8183280 commit 39c6a29
Show file tree
Hide file tree
Showing 54 changed files with 1,968 additions and 327 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import com.duckduckgo.di.scopes.AppScope
import com.facebook.flipper.android.AndroidFlipperClient
import com.facebook.flipper.core.FlipperPlugin
import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin
import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin
import com.facebook.soloader.SoLoader
import com.squareup.anvil.annotations.ContributesMultibinding
import timber.log.Timber
Expand All @@ -48,6 +49,7 @@ class FlipperInitializer @Inject constructor(

// Common device plugins
addPlugin(DatabasesFlipperPlugin(context))
addPlugin(SharedPreferencesFlipperPlugin(context))

start()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,25 @@ interface FeatureToggle {
*/
interface FeatureName {
val value: String

companion object {
/**
* Utility function to create a [FeatureName] from the passed in [block] lambda
* instead of using the anonymous `object : FeatureName` syntax.
*
* Usage:
*
* ```kotlin
* val feature = FeatureName {
*
* }
* ```
*/
inline operator fun invoke(crossinline block: () -> String): FeatureName {
return object : FeatureName {
override val value: String
get() = block()
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,3 @@ enum class PrivacyFeatureName(override val value: String) : FeatureName {
TrackingParametersFeatureName("trackingParameters"),
AutofillFeatureName("autofill"),
}

/**
* Convenience method to get the [PrivacyFeatureName] from its [String] value
*/
fun privacyFeatureValueOf(value: String): PrivacyFeatureName? {
return PrivacyFeatureName.values().find { it.value == value }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2022 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.privacy.config.api

import com.duckduckgo.feature.toggles.api.FeatureName

/**
* Implement this interface and contribute it as a multibinding to get called upon downloading remote privacy config
*
* Usage:
*
* ```kotlin
* @ContributesMultibinding(AppScope::class)
* class MuFeaturePlugin @Inject constructor(...) : PrivacyFeaturePlugin {
*
* }
* ```
*/
interface PrivacyFeaturePlugin {
/**
* @return `true` when the feature config was stored, otherwise `false`
*/
fun store(
name: FeatureName,
jsonString: String
): Boolean

/**
* @return the [FeatureName] of this feature
*/
val featureName: FeatureName
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ package com.duckduckgo.privacy.config.impl
import androidx.annotation.WorkerThread
import com.duckduckgo.app.global.plugins.PluginPoint
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.privacy.config.api.privacyFeatureValueOf
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.impl.models.JsonPrivacyConfig
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.PrivacyConfig
import com.duckduckgo.privacy.config.store.PrivacyConfigDatabase
import com.duckduckgo.privacy.config.store.PrivacyConfigRepository
Expand Down Expand Up @@ -58,10 +58,8 @@ class RealPrivacyConfigPersister @Inject constructor(
unprotectedTemporaryRepository.updateAll(jsonPrivacyConfig.unprotectedTemporary)
jsonPrivacyConfig.features.forEach { feature ->
feature.value?.let { jsonObject ->
privacyFeaturePluginPoint.getPlugins().forEach { plugin ->
privacyFeatureValueOf(feature.key)?.let {
plugin.store(it, jsonObject.toString())
}
privacyFeaturePluginPoint.getPlugins().firstOrNull { feature.key == it.featureName.value }?.let { featurePlugin ->
featurePlugin.store(FeatureName { feature.key }, jsonObject.toString())
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import com.duckduckgo.di.DaggerSet
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.privacy.config.impl.network.JSONObjectAdapter
import com.duckduckgo.privacy.config.impl.network.PrivacyConfigService
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePluginPoint
import com.duckduckgo.privacy.config.store.ALL_MIGRATIONS
import com.duckduckgo.privacy.config.store.PrivacyConfigDatabase
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2022 DuckDuckGo
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.duckduckgo.privacy.config.impl.features

import com.duckduckgo.privacy.config.api.PrivacyFeatureName

/**
* Convenience method to get the [PrivacyFeatureName] from its [String] value
*/
fun privacyFeatureValueOf(value: String): PrivacyFeatureName? {
return PrivacyFeatureName.values().find { it.value == value }
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.amplinks

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.*
import com.duckduckgo.privacy.config.store.features.amplinks.AmpLinksRepository
import com.squareup.anvil.annotations.ContributesMultibinding
Expand All @@ -32,7 +34,9 @@ class AmpLinksPlugin @Inject constructor(
private val privacyFeatureTogglesRepository: PrivacyFeatureTogglesRepository
) : PrivacyFeaturePlugin {

override fun store(name: PrivacyFeatureName, jsonString: String): Boolean {
override fun store(name: FeatureName, jsonString: String): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<AmpLinksFeature> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.autofill

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.AutofillExceptionEntity
import com.duckduckgo.privacy.config.store.PrivacyFeatureToggles
import com.duckduckgo.privacy.config.store.PrivacyFeatureTogglesRepository
Expand All @@ -35,9 +37,11 @@ class AutofillPlugin @Inject constructor(
) : PrivacyFeaturePlugin {

override fun store(
name: PrivacyFeatureName,
name: FeatureName,
jsonString: String
): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val autofillExceptions = mutableListOf<AutofillExceptionEntity>()
val moshi = Moshi.Builder().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.contentblocking

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.ContentBlockingExceptionEntity
import com.duckduckgo.privacy.config.store.PrivacyFeatureToggles
import com.duckduckgo.privacy.config.store.PrivacyFeatureTogglesRepository
Expand All @@ -35,9 +37,11 @@ class ContentBlockingPlugin @Inject constructor(
) : PrivacyFeaturePlugin {

override fun store(
name: PrivacyFeatureName,
name: FeatureName,
jsonString: String
): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val contentBlockingExceptions = mutableListOf<ContentBlockingExceptionEntity>()
val moshi = Moshi.Builder().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.drm

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.DrmExceptionEntity
import com.duckduckgo.privacy.config.store.PrivacyFeatureToggles
import com.duckduckgo.privacy.config.store.PrivacyFeatureTogglesRepository
Expand All @@ -35,9 +37,11 @@ class DrmPlugin @Inject constructor(
) : PrivacyFeaturePlugin {

override fun store(
name: PrivacyFeatureName,
name: FeatureName,
jsonString: String
): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val drmExceptions = mutableListOf<DrmExceptionEntity>()
val moshi = Moshi.Builder().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.gpc

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.GpcExceptionEntity
import com.duckduckgo.privacy.config.store.GpcHeaderEnabledSiteEntity
import com.duckduckgo.privacy.config.store.PrivacyFeatureToggles
Expand All @@ -36,9 +38,11 @@ class GpcPlugin @Inject constructor(
) : PrivacyFeaturePlugin {

override fun store(
name: PrivacyFeatureName,
name: FeatureName,
jsonString: String
): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val gpcExceptions = mutableListOf<GpcExceptionEntity>()
val gpcHeaders = mutableListOf<GpcHeaderEnabledSiteEntity>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.https

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.HttpsExceptionEntity
import com.duckduckgo.privacy.config.store.PrivacyFeatureToggles
import com.duckduckgo.privacy.config.store.PrivacyFeatureTogglesRepository
Expand All @@ -35,9 +37,11 @@ class HttpsPlugin @Inject constructor(
) : PrivacyFeaturePlugin {

override fun store(
name: PrivacyFeatureName,
name: FeatureName,
jsonString: String
): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val httpsExceptions = mutableListOf<HttpsExceptionEntity>()
val moshi = Moshi.Builder().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.trackerallowlist

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.TrackerAllowlistEntity
import com.duckduckgo.privacy.config.store.PrivacyFeatureToggles
import com.duckduckgo.privacy.config.store.PrivacyFeatureTogglesRepository
Expand All @@ -35,9 +37,11 @@ class TrackerAllowlistPlugin @Inject constructor(
) : PrivacyFeaturePlugin {

override fun store(
name: PrivacyFeatureName,
name: FeatureName,
jsonString: String
): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<TrackerAllowlistFeature> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package com.duckduckgo.privacy.config.impl.features.trackingparameters

import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.feature.toggles.api.FeatureName
import com.duckduckgo.privacy.config.api.PrivacyFeatureName
import com.duckduckgo.privacy.config.impl.plugins.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.impl.features.privacyFeatureValueOf
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin
import com.duckduckgo.privacy.config.store.*
import com.duckduckgo.privacy.config.store.features.trackingparameters.TrackingParametersRepository
import com.squareup.anvil.annotations.ContributesMultibinding
Expand All @@ -32,7 +34,9 @@ class TrackingParametersPlugin @Inject constructor(
private val privacyFeatureTogglesRepository: PrivacyFeatureTogglesRepository
) : PrivacyFeaturePlugin {

override fun store(name: PrivacyFeatureName, jsonString: String): Boolean {
override fun store(name: FeatureName, jsonString: String): Boolean {
@Suppress("NAME_SHADOWING")
val name = privacyFeatureValueOf(name.value)
if (name == featureName) {
val moshi = Moshi.Builder().build()
val jsonAdapter: JsonAdapter<TrackingParametersFeature> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,12 @@ package com.duckduckgo.privacy.config.impl.plugins

import com.duckduckgo.app.global.plugins.PluginPoint
import com.duckduckgo.di.DaggerSet
import com.duckduckgo.privacy.config.api.PrivacyFeatureName

interface PrivacyFeaturePlugin {
fun store(
name: PrivacyFeatureName,
jsonString: String
): Boolean

val featureName: PrivacyFeatureName
}
import com.duckduckgo.privacy.config.api.PrivacyFeaturePlugin

class PrivacyFeaturePluginPoint(
private val privacyFeatures: DaggerSet<PrivacyFeaturePlugin>
) : PluginPoint<PrivacyFeaturePlugin> {
override fun getPlugins(): Collection<PrivacyFeaturePlugin> {
return privacyFeatures
return privacyFeatures.sortedBy { it.featureName.value }
}
}
Loading

0 comments on commit 39c6a29

Please sign in to comment.