Skip to content

Commit

Permalink
Add algorithm pixels
Browse files Browse the repository at this point in the history
  • Loading branch information
CrisBarreiro committed Feb 5, 2025
1 parent f6a88b2 commit 2592f74
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@ import com.duckduckgo.app.browser.webview.ExemptedUrlsHolder.ExemptedUrl
import com.duckduckgo.app.browser.webview.RealMaliciousSiteBlockerWebViewIntegration.IsMaliciousViewData
import com.duckduckgo.app.di.AppCoroutineScope
import com.duckduckgo.app.di.IsMainProcess
import com.duckduckgo.app.pixels.AppPixelName
import com.duckduckgo.app.pixels.remoteconfig.AndroidBrowserConfigFeature
import com.duckduckgo.app.settings.db.SettingsDataStore
import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection
Expand Down Expand Up @@ -97,6 +99,7 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
@AppCoroutineScope private val appCoroutineScope: CoroutineScope,
private val exemptedUrlsHolder: ExemptedUrlsHolder,
@IsMainProcess private val isMainProcess: Boolean,
private val pixel: Pixel,
) : MaliciousSiteBlockerWebViewIntegration, PrivacyConfigCallbackPlugin {

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Expand Down Expand Up @@ -161,19 +164,31 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
}

val belongsToCurrentPage = documentUri?.host == request.requestHeaders["Referer"]?.toUri()?.host
if (request.isForMainFrame || (isForIframe(request) && belongsToCurrentPage)) {
when (val result = checkMaliciousUrl(decodedUrl, confirmationCallback)) {
val isForIframe = isForIframe(request) && belongsToCurrentPage
if (request.isForMainFrame || isForIframe) {
val result = checkMaliciousUrl(decodedUrl) {
if (isForIframe && it is Malicious) {
firePixelForMaliciousIframe(it.feed)
}
confirmationCallback(it)
}
when (result) {
is ConfirmedResult -> {
when (val status = result.status) {
is Malicious -> {
if (isForIframe) {
firePixelForMaliciousIframe(status.feed)
}
return IsMaliciousViewData.MaliciousSite(url, status.feed, false)
}

is Safe -> {
processedUrls.add(decodedUrl)
return IsMaliciousViewData.Safe
}
}
}

is WaitForConfirmation -> {
processedUrls.add(decodedUrl)
return IsMaliciousViewData.WaitForConfirmation
Expand Down Expand Up @@ -231,6 +246,10 @@ class RealMaliciousSiteBlockerWebViewIntegration @Inject constructor(
}
}

private fun firePixelForMaliciousIframe(feed: Feed) {
pixel.fire(AppPixelName.MALICIOUS_SITE_DETECTED_IN_IFRAME, mapOf("category" to feed.name.lowercase()))
}

private suspend fun checkMaliciousUrl(
url: String,
confirmationCallback: (maliciousStatus: MaliciousStatus) -> Unit,
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt
Original file line number Diff line number Diff line change
Expand Up @@ -387,4 +387,6 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
DEDICATED_WEBVIEW_URL_EXTRACTION_FAILED("m_dedicated_webview_url_extraction_failed"),

BLOCKLIST_TDS_FAILURE("blocklist_experiment_tds_download_failure"),

MALICIOUS_SITE_DETECTED_IN_IFRAME("m_malicious-site-protection_iframe-loaded"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ dependencies {

implementation Google.android.material

implementation project(path: ':statistics-api')

testImplementation AndroidX.test.ext.junit
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.5.2'
testImplementation Testing.junit4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2025 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.malicioussiteprotection.impl

import com.duckduckgo.app.statistics.pixels.Pixel

enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
MALICIOUS_SITE_CLIENT_TIMEOUT("m_malicious-site-protection_client-timeout"),
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@

package com.duckduckgo.malicioussiteprotection.impl.data

import com.duckduckgo.app.statistics.pixels.Pixel
import com.duckduckgo.common.utils.DispatcherProvider
import com.duckduckgo.di.scopes.AppScope
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.MALWARE
import com.duckduckgo.malicioussiteprotection.api.MaliciousSiteProtection.Feed.PHISHING
import com.duckduckgo.malicioussiteprotection.impl.AppPixelName.MALICIOUS_SITE_CLIENT_TIMEOUT
import com.duckduckgo.malicioussiteprotection.impl.data.db.MaliciousSiteDao
import com.duckduckgo.malicioussiteprotection.impl.data.db.RevisionEntity
import com.duckduckgo.malicioussiteprotection.impl.data.network.FilterResponse
Expand All @@ -41,6 +43,7 @@ import com.duckduckgo.malicioussiteprotection.impl.models.Type.FILTER_SET
import com.duckduckgo.malicioussiteprotection.impl.models.Type.HASH_PREFIXES
import com.squareup.anvil.annotations.ContributesBinding
import dagger.SingleInstanceIn
import java.util.concurrent.TimeoutException
import javax.inject.Inject
import kotlinx.coroutines.withContext

Expand All @@ -58,6 +61,7 @@ class RealMaliciousSiteRepository @Inject constructor(
private val maliciousSiteDao: MaliciousSiteDao,
private val maliciousSiteService: MaliciousSiteService,
private val dispatcherProvider: DispatcherProvider,
private val pixels: Pixel,
) : MaliciousSiteRepository {

override suspend fun containsHashPrefix(hashPrefix: String): Boolean {
Expand Down Expand Up @@ -91,6 +95,9 @@ class RealMaliciousSiteRepository @Inject constructor(
null
}
}
} catch (e: TimeoutException) {
pixels.fire(MALICIOUS_SITE_CLIENT_TIMEOUT)
listOf()
} catch (e: Exception) {
listOf()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ class MaliciousSiteProtectionRequestInterceptor @Inject constructor() : ApiInter
override fun intercept(chain: Chain): Response {
val request = chain.request()

val authRequired = chain.request().tag(Invocation::class.java)
?.method()
?.isAnnotationPresent(AuthRequired::class.java) == true
val method = chain.request().tag(Invocation::class.java)?.method()

val authRequired = method?.isAnnotationPresent(AuthRequired::class.java) == true

val timeoutAvailable = method?.isAnnotationPresent(Timeout::class.java) == true
if (timeoutAvailable) {
method?.getAnnotation(Timeout::class.java)?.let { chain.call().timeout().timeout(it.duration, it.unit) }
}

return if (authRequired) {
val newRequest = chain.request().newBuilder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import com.duckduckgo.anvil.annotations.ContributesServiceApi
import com.duckduckgo.common.utils.AppUrl.Url.API
import com.duckduckgo.di.scopes.AppScope
import com.squareup.moshi.Json
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeUnit.MILLISECONDS
import retrofit2.http.GET
import retrofit2.http.Query

Expand Down Expand Up @@ -48,6 +50,7 @@ interface MaliciousSiteService {
@GET("$BASE_URL$FILTER_SET_PATH?$CATEGORY=$MALWARE")
suspend fun getMalwareFilterSet(@Query("revision") revision: Int): FilterSetResponse

@Timeout(1000, MILLISECONDS)
@AuthRequired
@GET("$BASE_URL/matches")
suspend fun getMatches(@Query("hashPrefix") hashPrefix: String): MatchesResponse
Expand Down Expand Up @@ -99,3 +102,10 @@ data class RevisionResponse(
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class AuthRequired

/**
* This annotation is used in interceptors to be able to intercept the annotated service calls
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Timeout(val duration: Long, val unit: TimeUnit)

0 comments on commit 2592f74

Please sign in to comment.