Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Bitwarden import #120

Merged
merged 2 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ data class ExportJsonData(
val collectionIds: List<String>?,
val notes: String?,
val type: Int,
val login: ItemLoginData,
val login: ItemLoginData?,
val favorite: Boolean,
) {
/**
Expand All @@ -39,7 +39,7 @@ data class ExportJsonData(
*/
@Serializable
data class ItemLoginData(
val totp: String,
val totp: String?,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@

import android.net.Uri
import com.bitwarden.authenticator.data.authenticator.datasource.disk.AuthenticatorDiskSource
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemAlgorithm
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemEntity
import com.bitwarden.authenticator.data.authenticator.datasource.disk.entity.AuthenticatorItemType
import com.bitwarden.authenticator.data.authenticator.manager.FileManager
import com.bitwarden.authenticator.data.authenticator.manager.TotpCodeManager
import com.bitwarden.authenticator.data.authenticator.manager.model.ExportJsonData
Expand All @@ -24,8 +22,6 @@
import com.bitwarden.authenticator.data.platform.repository.util.bufferedMutableSharedFlow
import com.bitwarden.authenticator.data.platform.repository.util.combineDataStates
import com.bitwarden.authenticator.data.platform.repository.util.map
import com.bitwarden.authenticator.data.platform.util.asSuccess
import com.bitwarden.authenticator.data.platform.util.flatMap
import com.bitwarden.authenticator.ui.platform.feature.settings.export.model.ExportFormat
import com.bitwarden.authenticator.ui.platform.manager.intent.IntentManager
import kotlinx.coroutines.CoroutineScope
Expand All @@ -44,11 +40,8 @@
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import java.io.ByteArrayInputStream
import javax.inject.Inject

/**
Expand Down Expand Up @@ -259,7 +252,7 @@
): ImportDataResult = fileManager.uriToByteArray(fileData.uri)
.map { importManager.import(importFileFormat = format, byteArray = it) }
.fold(
onSuccess = { ImportDataResult.Success },
onSuccess = { it },

Check warning on line 255 in app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/bitwarden/authenticator/data/authenticator/repository/AuthenticatorRepositoryImpl.kt#L255

Added line #L255 was not covered by tests
onFailure = { ImportDataResult.Error }
)

Expand Down Expand Up @@ -321,106 +314,4 @@
),
favorite = false,
)

@OptIn(ExperimentalSerializationApi::class)
private suspend fun decodeVaultDataFromJson(fileUri: IntentManager.FileData): ImportDataResult {
val importJson = Json {
ignoreUnknownKeys = true
isLenient = true
explicitNulls = false
}
return try {
fileManager.uriToByteArray(fileUri.uri)
.flatMap {
importJson
.decodeFromStream<ExportJsonData>(ByteArrayInputStream(it))
.asSuccess()
}
.map { exportData ->
exportData
.items
.toAuthenticatorItemEntities()
}
.fold(
onSuccess = {
authenticatorDiskSource.saveItem(*it.toTypedArray())
ImportDataResult.Success
},
onFailure = {
ImportDataResult.Error
},
)
} catch (e: IllegalArgumentException) {
ImportDataResult.Error
}
}

private fun List<ExportJsonData.ExportItem>.toAuthenticatorItemEntities() =
map { it.toAuthenticatorItemEntity() }

private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity {
val otpString = login.totp
val otpUri = Uri.parse(otpString)
val type = if (otpString.startsWith(TotpCodeManager.TOTP_CODE_PREFIX)) {
AuthenticatorItemType.TOTP
} else if (otpString.startsWith(TotpCodeManager.STEAM_CODE_PREFIX)) {
AuthenticatorItemType.STEAM
} else {
throw IllegalArgumentException("Unsupported OTP type.")
}

val key = when (type) {
AuthenticatorItemType.TOTP -> {
requireNotNull(otpUri.getQueryParameter(TotpCodeManager.SECRET_PARAM))
}

AuthenticatorItemType.STEAM -> {
requireNotNull(otpUri.authority)
}
}

val algorithm = otpUri.getQueryParameter(TotpCodeManager.ALGORITHM_PARAM)
?: TotpCodeManager.ALGORITHM_DEFAULT.name

val period = otpUri.getQueryParameter(TotpCodeManager.PERIOD_PARAM)
?.toIntOrNull()
?: TotpCodeManager.PERIOD_SECONDS_DEFAULT

val digits = when (type) {
AuthenticatorItemType.TOTP -> {
otpUri.getQueryParameter(TotpCodeManager.DIGITS_PARAM)
?.toIntOrNull()
?: TotpCodeManager.TOTP_DIGITS_DEFAULT
}

AuthenticatorItemType.STEAM -> {
TotpCodeManager.STEAM_DIGITS_DEFAULT
}
}
val issuer = otpUri.getQueryParameter(TotpCodeManager.ISSUER_PARAM)
?: name

val label = when (type) {
AuthenticatorItemType.TOTP -> {
otpUri.pathSegments
.firstOrNull()
.orEmpty()
.removePrefix("$issuer:")
}

AuthenticatorItemType.STEAM -> null
}

return AuthenticatorItemEntity(
id = id,
key = key,
type = type,
algorithm = algorithm.let { AuthenticatorItemAlgorithm.valueOf(it) },
period = period,
digits = digits,
issuer = issuer,
accountName = label,
favorite = favorite,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
.mapCatching { exportData ->
exportData
.items
.filter { it.login?.totp != null }
.toAuthenticatorItemEntities()
}
} catch (e: SerializationException) {
Expand All @@ -56,11 +57,27 @@
map { it.toAuthenticatorItemEntity() }

private fun ExportJsonData.ExportItem.toAuthenticatorItemEntity(): AuthenticatorItemEntity {
val otpString = login.totp
val otpUri = Uri.parse(otpString)
val type = if (otpString.startsWith(TotpCodeManager.TOTP_CODE_PREFIX)) {
val otpString = requireNotNull(login?.totp)

val otpUri = when {

Check warning on line 62 in app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt#L62

Added line #L62 was not covered by tests
otpString.startsWith(TotpCodeManager.TOTP_CODE_PREFIX) -> {
Uri.parse(otpString)

Check warning on line 64 in app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt#L64

Added line #L64 was not covered by tests
}

otpString.startsWith(TotpCodeManager.STEAM_CODE_PREFIX) -> {
Uri.parse(otpString)

Check warning on line 68 in app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt#L68

Added line #L68 was not covered by tests
}

else -> {
val uriString =
"${TotpCodeManager.TOTP_CODE_PREFIX}/$name?${TotpCodeManager.SECRET_PARAM}=$otpString"
Uri.parse(uriString)

Check warning on line 74 in app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt

View check run for this annotation

Codecov / codecov/patch

app/src/main/kotlin/com/bitwarden/authenticator/data/platform/manager/imports/parsers/BitwardenExportParser.kt#L72-L74

Added lines #L72 - L74 were not covered by tests
}
}

val type = if (otpUri.scheme == "otpauth" && otpUri.authority == "totp") {
AuthenticatorItemType.TOTP
} else if (otpString.startsWith(TotpCodeManager.STEAM_CODE_PREFIX)) {
} else if (otpUri.scheme == "steam") {
AuthenticatorItemType.STEAM
} else {
throw IllegalArgumentException("Unsupported OTP type.")
Expand Down