Skip to content

Commit 24e174e

Browse files
committed
Write passkey logs to disk and copy to clipboard from settings > about
This commit introduces the ability to copy Passkey logs for debugging purposes. Key changes: - Added `writeToFile` and `readFromFile` functions to `FileManager` to handle file writing and reading operations. - Implemented `getPasskeyLogs` function in `LogsManager` to fetch the logs. - Introduced a PasskeyTree class in `LogsManagerImpl` to write Passkey related logs to a designated log file. - Added a "Copy passkey logs" option in the About screen to allow users to copy the logs to the clipboard. - Updated `AboutViewModel` to handle the copy passkey logs action.
1 parent 4cc3bf3 commit 24e174e

File tree

9 files changed

+192
-35
lines changed

9 files changed

+192
-35
lines changed

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/LogsManager.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,9 @@ interface LogsManager {
2121
* Tracks the current user data.
2222
*/
2323
fun setUserData(userId: String?, environmentType: Environment.Type)
24+
25+
/**
26+
* Gets the passkey logs.
27+
*/
28+
suspend fun getPasskeyLogs(): Result<String>
2429
}

app/src/main/java/com/x8bit/bitwarden/data/platform/manager/di/PlatformManagerModule.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
6868
import com.x8bit.bitwarden.data.platform.repository.ServerConfigRepository
6969
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
7070
import com.x8bit.bitwarden.data.vault.datasource.disk.VaultDiskSource
71+
import com.x8bit.bitwarden.data.vault.manager.FileManager
7172
import com.x8bit.bitwarden.data.vault.manager.VaultLockManager
7273
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
7374
import dagger.Module
@@ -257,9 +258,15 @@ object PlatformManagerModule {
257258
fun provideLogsManager(
258259
legacyAppCenterMigrator: LegacyAppCenterMigrator,
259260
settingsRepository: SettingsRepository,
261+
clock: Clock,
262+
fileManager: FileManager,
263+
dispatcherManager: DispatcherManager,
260264
): LogsManager = LogsManagerImpl(
261265
settingsRepository = settingsRepository,
262266
legacyAppCenterMigrator = legacyAppCenterMigrator,
267+
fileManager = fileManager,
268+
clock = clock,
269+
dispatcherManager = dispatcherManager,
263270
)
264271

265272
@Provides

app/src/main/java/com/x8bit/bitwarden/data/vault/manager/FileManager.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,14 @@ interface FileManager {
4949
* reference.
5050
*/
5151
suspend fun writeUriToCache(fileUri: Uri): Result<File>
52+
53+
/**
54+
* Writes [data] to a file on disk.
55+
*/
56+
suspend fun writeToFile(parent: String?, fileName: String, data: String)
57+
58+
/**
59+
* Reads the contents of a file on disk.
60+
*/
61+
suspend fun readFromFile(parent: String?, fileName: String): Result<String>
5262
}

app/src/main/java/com/x8bit/bitwarden/data/vault/manager/FileManagerImpl.kt

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import android.content.Context
44
import android.net.Uri
55
import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage
66
import com.x8bit.bitwarden.data.platform.manager.dispatcher.DispatcherManager
7+
import com.x8bit.bitwarden.data.platform.util.asFailure
8+
import com.x8bit.bitwarden.data.platform.util.asSuccess
79
import com.x8bit.bitwarden.data.platform.util.sdkAgnosticTransferTo
810
import com.x8bit.bitwarden.data.vault.datasource.network.service.DownloadService
911
import com.x8bit.bitwarden.data.vault.manager.model.DownloadResult
@@ -12,6 +14,7 @@ import java.io.ByteArrayOutputStream
1214
import java.io.File
1315
import java.io.FileInputStream
1416
import java.io.FileOutputStream
17+
import java.io.FileWriter
1518
import java.util.UUID
1619

1720
/**
@@ -154,4 +157,44 @@ class FileManagerImpl(
154157
File(context.filesDir, tempFileName)
155158
}
156159
}
160+
161+
@Suppress("TooGenericExceptionCaught")
162+
override suspend fun writeToFile(parent: String?, fileName: String, data: String) {
163+
withContext(dispatcherManager.io) {
164+
try {
165+
context.getExternalFilesDir(parent)
166+
?.let { logDir ->
167+
val file = File("${logDir.path}/$fileName")
168+
if (!file.exists()) {
169+
file.createNewFile()
170+
}
171+
FileWriter(File(logDir, fileName), true).use {
172+
it.write(data)
173+
it.flush()
174+
}
175+
}
176+
} catch (e: Exception) {
177+
e.printStackTrace()
178+
}
179+
}
180+
}
181+
182+
@Suppress("TooGenericExceptionCaught")
183+
override suspend fun readFromFile(parent: String?, fileName: String): Result<String> {
184+
return withContext(dispatcherManager.io) {
185+
try {
186+
context.getExternalFilesDir(parent)
187+
?.let { logDir ->
188+
val file = File("${logDir.path}/$fileName")
189+
if (!file.exists()) {
190+
return@withContext "Log file does not exist".asSuccess()
191+
}
192+
file.readText().asSuccess()
193+
}
194+
?: "Shared storage currently unavailable".asSuccess()
195+
} catch (e: Exception) {
196+
e.asFailure()
197+
}
198+
}
199+
}
157200
}

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutScreen.kt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.x8bit.bitwarden.ui.platform.feature.settings.about
22

3+
import android.content.Context
4+
import android.widget.Toast
35
import androidx.compose.foundation.layout.Box
46
import androidx.compose.foundation.layout.Column
57
import androidx.compose.foundation.layout.Spacer
@@ -21,6 +23,7 @@ import androidx.compose.runtime.remember
2123
import androidx.compose.ui.Alignment
2224
import androidx.compose.ui.Modifier
2325
import androidx.compose.ui.input.nestedscroll.nestedScroll
26+
import androidx.compose.ui.platform.LocalContext
2427
import androidx.compose.ui.platform.testTag
2528
import androidx.compose.ui.res.stringResource
2629
import androidx.compose.ui.tooling.preview.Preview
@@ -55,8 +58,10 @@ fun AboutScreen(
5558
onNavigateBack: () -> Unit,
5659
viewModel: AboutViewModel = hiltViewModel(),
5760
intentManager: IntentManager = LocalIntentManager.current,
61+
context: Context = LocalContext.current,
5862
) {
5963
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
64+
val resources = context.resources
6065
EventsEffect(viewModel = viewModel) { event ->
6166
when (event) {
6267
is AboutEvent.NavigateToWebVault -> {
@@ -84,9 +89,13 @@ fun AboutScreen(
8489
AboutEvent.NavigateToRateApp -> {
8590
intentManager.launchUri(
8691
uri =
87-
"https://play.google.com/store/apps/details?id=com.x8bit.bitwarden".toUri(),
92+
"https://play.google.com/store/apps/details?id=com.x8bit.bitwarden".toUri(),
8893
)
8994
}
95+
96+
is AboutEvent.ShowToast -> {
97+
Toast.makeText(context, event.text(resources), Toast.LENGTH_SHORT).show()
98+
}
9099
}
91100
}
92101

@@ -134,6 +143,9 @@ fun AboutScreen(
134143
onWebVaultClick = remember(viewModel) {
135144
{ viewModel.trySendAction(AboutAction.WebVaultClick) }
136145
},
146+
onCopyPasskeyLogsClick = remember(viewModel) {
147+
{ viewModel.trySendAction(AboutAction.CopyPasskeyLogsClick) }
148+
},
137149
)
138150
}
139151
}
@@ -150,6 +162,7 @@ private fun ContentColumn(
150162
onSubmitCrashLogsCheckedChange: (Boolean) -> Unit,
151163
onVersionClick: () -> Unit,
152164
onWebVaultClick: () -> Unit,
165+
onCopyPasskeyLogsClick: () -> Unit,
153166
modifier: Modifier = Modifier,
154167
) {
155168
Column(
@@ -245,11 +258,27 @@ private fun ContentColumn(
245258
CopyRow(
246259
text = state.version,
247260
onClick = onVersionClick,
261+
cardStyle = if (state.isSubmitCrashLogsEnabled) {
262+
CardStyle.Middle()
263+
} else {
264+
CardStyle.Bottom
265+
},
248266
modifier = Modifier
249267
.standardHorizontalMargin()
250268
.fillMaxWidth()
251269
.testTag("CopyAboutInfoRow"),
252270
)
271+
if (state.isSubmitCrashLogsEnabled) {
272+
CopyRow(
273+
text = R.string.copy_passkey_logs.asText(),
274+
onClick = onCopyPasskeyLogsClick,
275+
cardStyle = CardStyle.Bottom,
276+
modifier = Modifier
277+
.standardHorizontalMargin()
278+
.fillMaxWidth()
279+
.testTag("CopyPasskeyLogsRow"),
280+
)
281+
}
253282
Box(
254283
modifier = Modifier
255284
.defaultMinSize(minHeight = 60.dp)
@@ -271,13 +300,14 @@ private fun ContentColumn(
271300
private fun CopyRow(
272301
text: Text,
273302
onClick: () -> Unit,
303+
cardStyle: CardStyle,
274304
modifier: Modifier = Modifier,
275305
) {
276306
BitwardenTextRow(
277307
text = text(),
278308
onClick = onClick,
279309
withDivider = false,
280-
cardStyle = CardStyle.Bottom,
310+
cardStyle = cardStyle,
281311
modifier = modifier,
282312
) {
283313
Icon(
@@ -296,6 +326,7 @@ private fun CopyRow_preview() {
296326
CopyRow(
297327
text = "Copyable Text".asText(),
298328
onClick = { },
329+
cardStyle = CardStyle.Full,
299330
)
300331
}
301332
}

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/about/AboutViewModel.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
1919
import kotlinx.coroutines.flow.launchIn
2020
import kotlinx.coroutines.flow.onEach
2121
import kotlinx.coroutines.flow.update
22+
import kotlinx.coroutines.launch
2223
import kotlinx.parcelize.Parcelize
2324
import java.time.Clock
2425
import java.time.Year
@@ -29,6 +30,7 @@ private const val KEY_STATE = "state"
2930
/**
3031
* View model for the about screen.
3132
*/
33+
@Suppress("TooManyFunctions")
3234
@HiltViewModel
3335
class AboutViewModel @Inject constructor(
3436
private val savedStateHandle: SavedStateHandle,
@@ -58,6 +60,7 @@ class AboutViewModel @Inject constructor(
5860
is AboutAction.SubmitCrashLogsClick -> handleSubmitCrashLogsClick(action)
5961
AboutAction.VersionClick -> handleVersionClick()
6062
AboutAction.WebVaultClick -> handleWebVaultClick()
63+
AboutAction.CopyPasskeyLogsClick -> handleCopyPasskeyLogsClick()
6164
}
6265

6366
private fun handleBackClick() {
@@ -131,6 +134,24 @@ class AboutViewModel @Inject constructor(
131134
)
132135
}
133136

137+
private fun handleCopyPasskeyLogsClick() {
138+
viewModelScope.launch {
139+
logsManager
140+
.getPasskeyLogs()
141+
.fold(
142+
onSuccess = { logs ->
143+
clipboardManager.setText(
144+
text = logs,
145+
toastDescriptorOverride = R.string.passkey_logs.asText(),
146+
)
147+
},
148+
onFailure = {
149+
sendEvent(AboutEvent.ShowToast(R.string.error_copying_logs.asText()))
150+
},
151+
)
152+
}
153+
}
154+
134155
@Suppress("UndocumentedPublicClass")
135156
companion object {
136157
/**
@@ -201,6 +222,11 @@ sealed class AboutEvent {
201222
* Navigates to rate the app.
202223
*/
203224
data object NavigateToRateApp : AboutEvent()
225+
226+
/**
227+
* Show a toast.
228+
*/
229+
data class ShowToast(val text: Text) : AboutEvent()
204230
}
205231

206232
/**
@@ -253,4 +279,9 @@ sealed class AboutAction {
253279
* User clicked the web vault row.
254280
*/
255281
data object WebVaultClick : AboutAction()
282+
283+
/**
284+
* User clicked the copy passkey logs row.
285+
*/
286+
data object CopyPasskeyLogsClick : AboutAction()
256287
}

app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt

Lines changed: 1 addition & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1495,41 +1495,11 @@ class VaultItemListingViewModel @Inject constructor(
14951495
}
14961496

14971497
private fun handleFido2OriginValidationFail(error: Fido2ValidateOriginResult.Error) {
1498-
val messageResId = when (error) {
1499-
Fido2ValidateOriginResult.Error.ApplicationNotFound -> {
1500-
R.string.passkey_operation_failed_because_app_not_found_in_asset_links
1501-
}
1502-
1503-
Fido2ValidateOriginResult.Error.ApplicationFingerprintNotVerified -> {
1504-
R.string.passkey_operation_failed_because_app_could_not_be_verified
1505-
}
1506-
1507-
Fido2ValidateOriginResult.Error.AssetLinkNotFound -> {
1508-
R.string.passkey_operation_failed_because_of_missing_asset_links
1509-
}
1510-
1511-
Fido2ValidateOriginResult.Error.PrivilegedAppNotAllowed -> {
1512-
R.string.passkey_operation_failed_because_browser_is_not_privileged
1513-
}
1514-
1515-
Fido2ValidateOriginResult.Error.PasskeyNotSupportedForApp -> {
1516-
R.string.passkeys_not_supported_for_this_app
1517-
}
1518-
1519-
Fido2ValidateOriginResult.Error.PrivilegedAppSignatureNotFound -> {
1520-
R.string.passkey_operation_failed_because_browser_signature_does_not_match
1521-
}
1522-
1523-
Fido2ValidateOriginResult.Error.Unknown -> {
1524-
Timber.tag("PASSKEY").e("FIDO2 origin validation failed with unknown error.")
1525-
R.string.generic_error_message
1526-
}
1527-
}
15281498
mutableStateFlow.update {
15291499
it.copy(
15301500
dialogState = VaultItemListingState.DialogState.Fido2OperationFail(
15311501
title = R.string.an_error_has_occurred.asText(),
1532-
message = messageResId.asText(),
1502+
message = error.messageResId.asText(),
15331503
),
15341504
)
15351505
}

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,4 +1170,7 @@ Do you want to switch to this account?</string>
11701170
<string name="passkey_registration_failed_due_to_an_internal_error">Passkey registration failed due to an internal error.</string>
11711171
<string name="passkey_operation_failed_because_relying_party_cannot_be_identified">Passkey operation failed because relying party cannot be identified.</string>
11721172
<string name="passkey_authentication_failed_due_to_an_internal_error">Passkey authentication failed due to an internal error.</string>
1173+
<string name="copy_passkey_logs">Copy passkey logs</string>
1174+
<string name="passkey_logs">Passkey logs</string>
1175+
<string name="error_copying_logs">Error copying logs</string>
11731176
</resources>

0 commit comments

Comments
 (0)