From 95d45a894464a810448dc271bad90053b2b14938 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 16 Mar 2026 16:03:43 +0100 Subject: [PATCH 1/7] Improve application password storing error logs for debugging Add detailed field-level diagnostics to all error paths in the application password login flow so developers can identify the root cause from logs alone. Co-Authored-By: Claude Opus 4.6 --- .../ApplicationPasswordLoginViewModel.kt | 37 +++++++++++++++---- .../login/ApplicationPasswordLoginHelper.kt | 18 ++++++++- ...licationPasswordAutoAuthDialogViewModel.kt | 20 ++++++++-- 3 files changed, 63 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt index b529272367c4..afaf730ef3cb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt @@ -62,7 +62,7 @@ class ApplicationPasswordLoginViewModel @Inject constructor( fun setupSite(rawData: String) { viewModelScope.launch { if (rawData.isEmpty()) { - appLogWrapper.e(AppLog.T.MAIN, "Cannot store credentials: rawData is empty") + appLogWrapper.e(AppLog.T.MAIN, "A_P: Cannot store credentials: rawData is empty") _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, @@ -95,7 +95,10 @@ class ApplicationPasswordLoginViewModel @Inject constructor( try { applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom(urlLogin) } catch (e: Exception) { - appLogWrapper.e(AppLog.T.DB, "Error storing credentials: ${e.stackTraceToString()}") + appLogWrapper.e( + AppLog.T.DB, + "A_P: Error storing credentials: ${e.stackTraceToString()}" + ) false } } @@ -109,9 +112,14 @@ class ApplicationPasswordLoginViewModel @Inject constructor( ) = withContext(ioDispatcher) { try { if (username.isEmpty() || password.isEmpty() || siteUrl.isEmpty() || apiRootUrl.isEmpty()) { - appLogWrapper.e(AppLog.T.MAIN, "Cannot fetch sites for credential storing: " + - "Username: $username, Password: ${password.isEmpty()}, SiteUrl: $siteUrl, " + - "API Root URL: $apiRootUrl") + appLogWrapper.e( + AppLog.T.MAIN, + "A_P: Cannot fetch sites for credential storing" + + " - username isEmpty=${username.isEmpty()}" + + ", password isEmpty=${password.isEmpty()}" + + ", siteUrl isEmpty=${siteUrl.isEmpty()}" + + ", apiRootUrl isEmpty=${apiRootUrl.isEmpty()}" + ) emitErrorFetching(siteUrl) } else { val xmlRpcEndpoint = @@ -128,7 +136,10 @@ class ApplicationPasswordLoginViewModel @Inject constructor( ) } } catch (e: Exception) { - appLogWrapper.e(AppLog.T.API, "Error fetching sites: ${e.stackTraceToString()}") + appLogWrapper.e( + AppLog.T.API, + "A_P: Error fetching sites: ${e.stackTraceToString()}" + ) emitErrorFetching(siteUrl) } } @@ -150,7 +161,19 @@ class ApplicationPasswordLoginViewModel @Inject constructor( val currentNormalizedUrl = UrlUtils.normalizeUrl(currentUrlLogin?.siteUrl) val site = siteStore.sites.firstOrNull { UrlUtils.normalizeUrl(it.url) == currentNormalizedUrl } if (event.rowsAffected < 1 || site == null || applicationPasswordLoginHelper.siteHasBadCredentials(site)) { - appLogWrapper.e(AppLog.T.MAIN, "Site not found or credentials are empty.") + appLogWrapper.e( + AppLog.T.MAIN, + "A_P: onSiteChanged failed" + + " for: ${currentUrlLogin?.siteUrl}" + + " - rowsAffected=${event.rowsAffected}" + + ", siteFound=${site != null}" + + ", badCredentials=${ + site?.let { + applicationPasswordLoginHelper + .siteHasBadCredentials(it) + } + }" + ) _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt index 1b9f4d39680d..5ae1edadc16d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt @@ -91,7 +91,14 @@ class ApplicationPasswordLoginHelper @Inject constructor( ) { appLogWrapper.e( AppLog.T.DB, - "A_P: Cannot save application password credentials for: ${urlLogin.siteUrl} - bad data" + "A_P: Cannot save application password credentials" + + " for: ${urlLogin.siteUrl}" + + " - apiRootUrl isNull=${urlLogin.apiRootUrl == null}" + + ", user isEmpty=${urlLogin.user.isNullOrEmpty()}" + + ", password isEmpty=${urlLogin.password.isNullOrEmpty()}" + + ", siteUrl isNull=${urlLogin.siteUrl == null}" + + ", alreadyProcessed=" + + "${urlLogin.siteUrl == processedAppPasswordData}" ) return false } @@ -114,9 +121,16 @@ class ApplicationPasswordLoginHelper @Inject constructor( processedAppPasswordData = urlLogin.siteUrl // Save locally to avoid duplicated calls true } else { + val availableSiteUrls = siteStore.sites.map { + UrlUtils.normalizeUrl(it.url) + } appLogWrapper.e( AppLog.T.DB, - "A_P: Cannot save application password credentials for: ${urlLogin.siteUrl} - null site" + "A_P: Cannot save application password" + + " credentials for: ${urlLogin.siteUrl}" + + " (normalized: $normalizedUrl)" + + " - site not found in store." + + " Available sites: $availableSiteUrls" ) false } diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt index 39689966d8f9..3a52ba063b5d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -89,12 +89,22 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( } else -> { - appLogWrapper.e(AppLog.T.API, "Error creating application password") + appLogWrapper.e( + AppLog.T.API, + "A_P: Error creating application password" + + " for: ${site.url}" + + " - response: $response" + ) fallbackToManualLogin(site.url) } } } catch (e: Exception) { - appLogWrapper.e(AppLog.T.API, "Exception creating application password: ${e.message}") + appLogWrapper.e( + AppLog.T.API, + "A_P: Exception creating application password" + + " for: ${site.url}" + + " - ${e.message}" + ) fallbackToManualLogin(site.url) } finally { _isLoading.value = false @@ -115,7 +125,11 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( val authUrl = applicationPasswordLoginHelper.getAuthorizationUrlComplete(siteUrl) _navigationEvent.emit(NavigationEvent.FallbackToManualLogin(authUrl)) } catch (e: Exception) { - appLogWrapper.e(AppLog.T.API, "Failed to get authorization URL: ${e.message}") + appLogWrapper.e( + AppLog.T.API, + "A_P: Failed to get authorization URL" + + " for: $siteUrl - ${e.message}" + ) _navigationEvent.emit(NavigationEvent.Error) } } From 46d3cf5c8e6a89fbb0434b320b840d359c2612ca Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 16 Mar 2026 16:12:18 +0100 Subject: [PATCH 2/7] Avoid logging full response object to prevent potential data leak Log only the response class name instead of the full object, since WpRequestResult variants could contain sensitive API response data. Co-Authored-By: Claude Opus 4.6 --- .../ApplicationPasswordAutoAuthDialogViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt index 3a52ba063b5d..582fbe032c9a 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -93,7 +93,7 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( AppLog.T.API, "A_P: Error creating application password" + " for: ${site.url}" + - " - response: $response" + " - response type: ${response::class.simpleName}" ) fallbackToManualLogin(site.url) } From df78e541bed68f8e178abedcc8af3010c3b32eb9 Mon Sep 17 00:00:00 2001 From: adalpari Date: Mon, 16 Mar 2026 17:15:25 +0100 Subject: [PATCH 3/7] Extract log helper to fix detekt LongMethod violation The createApplicationPassword function exceeded the 60-line limit after adding detailed error logging. Extract a logCreationError helper to keep the method concise. Co-Authored-By: Claude Opus 4.6 --- ...licationPasswordAutoAuthDialogViewModel.kt | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt index 582fbe032c9a..36afdc5fe87e 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/applicationpassword/ApplicationPasswordAutoAuthDialogViewModel.kt @@ -89,22 +89,12 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( } else -> { - appLogWrapper.e( - AppLog.T.API, - "A_P: Error creating application password" + - " for: ${site.url}" + - " - response type: ${response::class.simpleName}" - ) + logCreationError(site.url, "response type: ${response::class.simpleName}") fallbackToManualLogin(site.url) } } } catch (e: Exception) { - appLogWrapper.e( - AppLog.T.API, - "A_P: Exception creating application password" + - " for: ${site.url}" + - " - ${e.message}" - ) + logCreationError(site.url, e.message.orEmpty()) fallbackToManualLogin(site.url) } finally { _isLoading.value = false @@ -112,6 +102,13 @@ class ApplicationPasswordAutoAuthDialogViewModel @Inject constructor( } } + private fun logCreationError(siteUrl: String, detail: String) { + appLogWrapper.e( + AppLog.T.API, + "A_P: Error creating application password for: $siteUrl - $detail" + ) + } + @Suppress("TooGenericExceptionCaught") private fun enableApplicationPasswordIfNecessary() { if (!experimentalFeatures.isEnabled(Feature.EXPERIMENTAL_APPLICATION_PASSWORD_FEATURE)) { From f27cc9ef78814038ddfdeece15ad7686b61f5731 Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 17 Mar 2026 09:25:43 +0100 Subject: [PATCH 4/7] Fix test expecting single siteStore.sites call The new logging in the "site not found" branch reads siteStore.sites a second time to log available URLs. Update the test verification to expect two calls. Co-Authored-By: Claude Opus 4.6 --- .../ui/accounts/login/ApplicationPasswordLoginHelperTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt index 9c4a55e708a8..84a085d8440a 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt @@ -150,7 +150,7 @@ class ApplicationPasswordLoginHelperTest : BaseUnitTest() { val result = applicationPasswordLoginHelper.storeApplicationPasswordCredentialsFrom(testUriLogin) assertFalse(result) - verify(siteStore).sites + verify(siteStore, times(2)).sites verify(dispatcherWrapper, times(0)).updateApplicationPassword(any()) verify(dispatcherWrapper, times(0)).removeApplicationPassword(any()) } From 90319f7217d11739429598921ab02b8d5e265181 Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 17 Mar 2026 12:10:14 +0100 Subject: [PATCH 5/7] Add Tracks and Sentry events for application password storing failures Fire APPLICATION_PASSWORD_STORING_FAILED Tracks event and a Sentry report whenever the credential storing flow fails, so we can monitor failure rates and reasons in production. Also replace available site URL logging with just a count to avoid PII exposure. Co-Authored-By: Claude Opus 4.6 --- .../ApplicationPasswordLoginViewModel.kt | 11 +++++++ .../login/ApplicationPasswordLoginHelper.kt | 31 +++++++++++++++---- .../ApplicationPasswordLoginHelperTest.kt | 7 ++++- .../android/analytics/AnalyticsTracker.java | 3 +- 4 files changed, 44 insertions(+), 8 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt index afaf730ef3cb..ababe0104ac0 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt @@ -63,6 +63,7 @@ class ApplicationPasswordLoginViewModel @Inject constructor( viewModelScope.launch { if (rawData.isEmpty()) { appLogWrapper.e(AppLog.T.MAIN, "A_P: Cannot store credentials: rawData is empty") + applicationPasswordLoginHelper.trackStoringFailed("", "empty_raw_data") _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, @@ -120,6 +121,9 @@ class ApplicationPasswordLoginViewModel @Inject constructor( ", siteUrl isEmpty=${siteUrl.isEmpty()}" + ", apiRootUrl isEmpty=${apiRootUrl.isEmpty()}" ) + applicationPasswordLoginHelper.trackStoringFailed( + siteUrl, "empty_fetch_params" + ) emitErrorFetching(siteUrl) } else { val xmlRpcEndpoint = @@ -140,6 +144,9 @@ class ApplicationPasswordLoginViewModel @Inject constructor( AppLog.T.API, "A_P: Error fetching sites: ${e.stackTraceToString()}" ) + applicationPasswordLoginHelper.trackStoringFailed( + siteUrl, "fetch_sites_exception" + ) emitErrorFetching(siteUrl) } } @@ -174,6 +181,10 @@ class ApplicationPasswordLoginViewModel @Inject constructor( } }" ) + applicationPasswordLoginHelper.trackStoringFailed( + currentUrlLogin?.siteUrl, + "site_changed_failed" + ) _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt index 5ae1edadc16d..982736eecf69 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt @@ -1,6 +1,7 @@ package org.wordpress.android.ui.accounts.login import androidx.core.net.toUri +import com.automattic.android.tracks.crashlogging.CrashLogging import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import org.wordpress.android.analytics.AnalyticsTracker @@ -14,6 +15,7 @@ import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.util.AppLog import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.util.UrlUtils +import org.wordpress.android.util.crashlogging.sendReportWithTag import rs.wordpress.api.kotlin.ApiDiscoveryResult import rs.wordpress.api.kotlin.WpLoginClient import uniffi.wp_api.applicationPasswordsUrl @@ -22,6 +24,7 @@ import javax.inject.Named private const val URL_TAG = "url" private const val SUCCESS_TAG = "success" +private const val REASON_TAG = "reason" class ApplicationPasswordLoginHelper @Inject constructor( @param:Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, @@ -32,7 +35,8 @@ class ApplicationPasswordLoginHelper @Inject constructor( private val wpLoginClient: WpLoginClient, private val appLogWrapper: AppLogWrapper, private val apiRootUrlCache: ApiRootUrlCache, - private val discoverSuccessWrapper: DiscoverSuccessWrapper + private val discoverSuccessWrapper: DiscoverSuccessWrapper, + private val crashLogging: CrashLogging ) { private var processedAppPasswordData: String? = null @@ -100,6 +104,7 @@ class ApplicationPasswordLoginHelper @Inject constructor( ", alreadyProcessed=" + "${urlLogin.siteUrl == processedAppPasswordData}" ) + trackStoringFailed(urlLogin.siteUrl, "bad_data") return false } @@ -121,22 +126,36 @@ class ApplicationPasswordLoginHelper @Inject constructor( processedAppPasswordData = urlLogin.siteUrl // Save locally to avoid duplicated calls true } else { - val availableSiteUrls = siteStore.sites.map { - UrlUtils.normalizeUrl(it.url) - } appLogWrapper.e( AppLog.T.DB, "A_P: Cannot save application password" + " credentials for: ${urlLogin.siteUrl}" + " (normalized: $normalizedUrl)" + - " - site not found in store." + - " Available sites: $availableSiteUrls" + " - site not found in store" + + " (${siteStore.sites.size} sites available)" ) + trackStoringFailed(urlLogin.siteUrl, "site_not_found") false } } } + fun trackStoringFailed(siteUrl: String?, reason: String) { + val properties: MutableMap = HashMap() + properties[URL_TAG] = siteUrl + properties[REASON_TAG] = reason + AnalyticsTracker.track( + Stat.APPLICATION_PASSWORD_STORING_FAILED, + properties + ) + crashLogging.sendReportWithTag( + exception = Exception( + "A_P: storing failed for $siteUrl - $reason" + ), + tag = AppLog.T.DB + ) + } + private fun trackSuccessful(siteUrl: String) { val properties: MutableMap = HashMap() properties[URL_TAG] = siteUrl diff --git a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt index 84a085d8440a..5f0a9a704f95 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt @@ -4,6 +4,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test +import com.automattic.android.tracks.crashlogging.CrashLogging import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @@ -63,6 +64,9 @@ class ApplicationPasswordLoginHelperTest : BaseUnitTest() { @Mock lateinit var apiRootUrlCache: ApiRootUrlCache + @Mock + lateinit var crashLogging: CrashLogging + private lateinit var applicationPasswordLoginHelper: ApplicationPasswordLoginHelper @Before @@ -77,7 +81,8 @@ class ApplicationPasswordLoginHelperTest : BaseUnitTest() { wpLoginClient, appLogWrapper, apiRootUrlCache, - discoverSuccessWrapper + discoverSuccessWrapper, + crashLogging ) } diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index 6ecaef5b2a47..a9a16a2fbf16 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -1125,7 +1125,8 @@ public enum Stat { BACKGROUND_REST_AUTODISCOVERY_FAILED, WP_ANDROID_APPLICATION_PASSWORD_LOGIN, JP_ANDROID_APPLICATION_PASSWORD_LOGIN, - APPLICATION_PASSWORD_SET_OFF; + APPLICATION_PASSWORD_SET_OFF, + APPLICATION_PASSWORD_STORING_FAILED; /* * Please set the event name in the enum only if the new Stat's name in lower case does not match it. * In that case you also need to add the event in the `AnalyticsTrackerNosaraTest.specialNames` map. From 2b3baa61101247c4c20775e47212878181426fad Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 17 Mar 2026 12:10:57 +0100 Subject: [PATCH 6/7] Revert "Add Tracks and Sentry events for application password storing failures" This reverts commit 90319f7217d11739429598921ab02b8d5e265181. --- .../ApplicationPasswordLoginViewModel.kt | 11 ------- .../login/ApplicationPasswordLoginHelper.kt | 31 ++++--------------- .../ApplicationPasswordLoginHelperTest.kt | 7 +---- .../android/analytics/AnalyticsTracker.java | 3 +- 4 files changed, 8 insertions(+), 44 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt index ababe0104ac0..afaf730ef3cb 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/applicationpassword/ApplicationPasswordLoginViewModel.kt @@ -63,7 +63,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( viewModelScope.launch { if (rawData.isEmpty()) { appLogWrapper.e(AppLog.T.MAIN, "A_P: Cannot store credentials: rawData is empty") - applicationPasswordLoginHelper.trackStoringFailed("", "empty_raw_data") _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, @@ -121,9 +120,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( ", siteUrl isEmpty=${siteUrl.isEmpty()}" + ", apiRootUrl isEmpty=${apiRootUrl.isEmpty()}" ) - applicationPasswordLoginHelper.trackStoringFailed( - siteUrl, "empty_fetch_params" - ) emitErrorFetching(siteUrl) } else { val xmlRpcEndpoint = @@ -144,9 +140,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( AppLog.T.API, "A_P: Error fetching sites: ${e.stackTraceToString()}" ) - applicationPasswordLoginHelper.trackStoringFailed( - siteUrl, "fetch_sites_exception" - ) emitErrorFetching(siteUrl) } } @@ -181,10 +174,6 @@ class ApplicationPasswordLoginViewModel @Inject constructor( } }" ) - applicationPasswordLoginHelper.trackStoringFailed( - currentUrlLogin?.siteUrl, - "site_changed_failed" - ) _onFinishedEvent.emit( NavigationActionData( showSiteSelector = false, diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt index 982736eecf69..5ae1edadc16d 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt @@ -1,7 +1,6 @@ package org.wordpress.android.ui.accounts.login import androidx.core.net.toUri -import com.automattic.android.tracks.crashlogging.CrashLogging import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.withContext import org.wordpress.android.analytics.AnalyticsTracker @@ -15,7 +14,6 @@ import org.wordpress.android.modules.BG_THREAD import org.wordpress.android.util.AppLog import org.wordpress.android.util.BuildConfigWrapper import org.wordpress.android.util.UrlUtils -import org.wordpress.android.util.crashlogging.sendReportWithTag import rs.wordpress.api.kotlin.ApiDiscoveryResult import rs.wordpress.api.kotlin.WpLoginClient import uniffi.wp_api.applicationPasswordsUrl @@ -24,7 +22,6 @@ import javax.inject.Named private const val URL_TAG = "url" private const val SUCCESS_TAG = "success" -private const val REASON_TAG = "reason" class ApplicationPasswordLoginHelper @Inject constructor( @param:Named(BG_THREAD) private val bgDispatcher: CoroutineDispatcher, @@ -35,8 +32,7 @@ class ApplicationPasswordLoginHelper @Inject constructor( private val wpLoginClient: WpLoginClient, private val appLogWrapper: AppLogWrapper, private val apiRootUrlCache: ApiRootUrlCache, - private val discoverSuccessWrapper: DiscoverSuccessWrapper, - private val crashLogging: CrashLogging + private val discoverSuccessWrapper: DiscoverSuccessWrapper ) { private var processedAppPasswordData: String? = null @@ -104,7 +100,6 @@ class ApplicationPasswordLoginHelper @Inject constructor( ", alreadyProcessed=" + "${urlLogin.siteUrl == processedAppPasswordData}" ) - trackStoringFailed(urlLogin.siteUrl, "bad_data") return false } @@ -126,36 +121,22 @@ class ApplicationPasswordLoginHelper @Inject constructor( processedAppPasswordData = urlLogin.siteUrl // Save locally to avoid duplicated calls true } else { + val availableSiteUrls = siteStore.sites.map { + UrlUtils.normalizeUrl(it.url) + } appLogWrapper.e( AppLog.T.DB, "A_P: Cannot save application password" + " credentials for: ${urlLogin.siteUrl}" + " (normalized: $normalizedUrl)" + - " - site not found in store" + - " (${siteStore.sites.size} sites available)" + " - site not found in store." + + " Available sites: $availableSiteUrls" ) - trackStoringFailed(urlLogin.siteUrl, "site_not_found") false } } } - fun trackStoringFailed(siteUrl: String?, reason: String) { - val properties: MutableMap = HashMap() - properties[URL_TAG] = siteUrl - properties[REASON_TAG] = reason - AnalyticsTracker.track( - Stat.APPLICATION_PASSWORD_STORING_FAILED, - properties - ) - crashLogging.sendReportWithTag( - exception = Exception( - "A_P: storing failed for $siteUrl - $reason" - ), - tag = AppLog.T.DB - ) - } - private fun trackSuccessful(siteUrl: String) { val properties: MutableMap = HashMap() properties[URL_TAG] = siteUrl diff --git a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt index 5f0a9a704f95..84a085d8440a 100644 --- a/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt +++ b/WordPress/src/test/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelperTest.kt @@ -4,7 +4,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -import com.automattic.android.tracks.crashlogging.CrashLogging import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations @@ -64,9 +63,6 @@ class ApplicationPasswordLoginHelperTest : BaseUnitTest() { @Mock lateinit var apiRootUrlCache: ApiRootUrlCache - @Mock - lateinit var crashLogging: CrashLogging - private lateinit var applicationPasswordLoginHelper: ApplicationPasswordLoginHelper @Before @@ -81,8 +77,7 @@ class ApplicationPasswordLoginHelperTest : BaseUnitTest() { wpLoginClient, appLogWrapper, apiRootUrlCache, - discoverSuccessWrapper, - crashLogging + discoverSuccessWrapper ) } diff --git a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java index a9a16a2fbf16..6ecaef5b2a47 100644 --- a/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java +++ b/libs/analytics/src/main/java/org/wordpress/android/analytics/AnalyticsTracker.java @@ -1125,8 +1125,7 @@ public enum Stat { BACKGROUND_REST_AUTODISCOVERY_FAILED, WP_ANDROID_APPLICATION_PASSWORD_LOGIN, JP_ANDROID_APPLICATION_PASSWORD_LOGIN, - APPLICATION_PASSWORD_SET_OFF, - APPLICATION_PASSWORD_STORING_FAILED; + APPLICATION_PASSWORD_SET_OFF; /* * Please set the event name in the enum only if the new Stat's name in lower case does not match it. * In that case you also need to add the event in the `AnalyticsTrackerNosaraTest.specialNames` map. From 32001d3669bde4507ade09b8ff8e97120584e576 Mon Sep 17 00:00:00 2001 From: adalpari Date: Tue, 17 Mar 2026 12:12:12 +0100 Subject: [PATCH 7/7] Avoid logging user site URLs to prevent PII exposure Replace the full list of available site URLs with just a count in the "site not found" error log, since site URLs may reveal personal domains or business names. Co-Authored-By: Claude Opus 4.6 --- .../ui/accounts/login/ApplicationPasswordLoginHelper.kt | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt index 5ae1edadc16d..b728c1fe8f6f 100644 --- a/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt +++ b/WordPress/src/main/java/org/wordpress/android/ui/accounts/login/ApplicationPasswordLoginHelper.kt @@ -121,16 +121,13 @@ class ApplicationPasswordLoginHelper @Inject constructor( processedAppPasswordData = urlLogin.siteUrl // Save locally to avoid duplicated calls true } else { - val availableSiteUrls = siteStore.sites.map { - UrlUtils.normalizeUrl(it.url) - } appLogWrapper.e( AppLog.T.DB, "A_P: Cannot save application password" + " credentials for: ${urlLogin.siteUrl}" + " (normalized: $normalizedUrl)" + - " - site not found in store." + - " Available sites: $availableSiteUrls" + " - site not found in store" + + " (${siteStore.sites.size} sites available)" ) false }