mirror of
https://github.com/bitwarden/android.git
synced 2026-03-12 05:04:17 -05:00
[BWA-16] Import Google Authenticator exports via scanner (#101)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import com.google.protobuf.gradle.proto
|
||||
|
||||
plugins {
|
||||
alias(libs.plugins.android.application)
|
||||
alias(libs.plugins.crashlytics)
|
||||
@@ -7,6 +9,7 @@ plugins {
|
||||
alias(libs.plugins.kotlin.serialization)
|
||||
alias(libs.plugins.kotlinx.kover)
|
||||
alias(libs.plugins.ksp)
|
||||
alias(libs.plugins.google.protobuf)
|
||||
alias(libs.plugins.google.services)
|
||||
alias(libs.plugins.sonarqube)
|
||||
}
|
||||
@@ -65,6 +68,13 @@ android {
|
||||
excludes += "/META-INF/{AL2.0,LGPL2.1,LICENSE*.md}"
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
getByName("main") {
|
||||
proto {
|
||||
srcDir("src/main/proto")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@@ -103,10 +113,12 @@ dependencies {
|
||||
implementation(libs.google.firebase.crashlytics)
|
||||
implementation(libs.google.hilt.android)
|
||||
ksp(libs.google.hilt.compiler)
|
||||
implementation(libs.google.guava)
|
||||
implementation(libs.google.protobuf.javalite)
|
||||
implementation(libs.jakewharton.retrofit.kotlinx.serialization)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.kotlinx.coroutines.android)
|
||||
implementation(libs.kotlinx.serialization)
|
||||
implementation(libs.kotlinx.serialization.json)
|
||||
implementation(libs.square.okhttp)
|
||||
implementation(libs.square.okhttp.logging)
|
||||
implementation(libs.square.retrofit)
|
||||
@@ -129,6 +141,19 @@ dependencies {
|
||||
androidTestImplementation(libs.bundles.tests.instrumented)
|
||||
}
|
||||
|
||||
protobuf {
|
||||
protoc {
|
||||
artifact = libs.google.protobuf.protoc.get().toString()
|
||||
}
|
||||
generateProtoTasks {
|
||||
this.all().forEach { task ->
|
||||
task.builtins.create("java") {
|
||||
option("lite")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sonar {
|
||||
properties {
|
||||
property("sonar.projectKey", "bitwarden_authenticator-android")
|
||||
@@ -140,7 +165,10 @@ sonar {
|
||||
}
|
||||
|
||||
tasks {
|
||||
withType<Test> {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
getByName("sonar") {
|
||||
dependsOn("check")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
app/proguard-rules.pro
vendored
6
app/proguard-rules.pro
vendored
@@ -53,6 +53,12 @@
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
|
||||
################################################################################
|
||||
# Google Protobuf generated files
|
||||
################################################################################
|
||||
|
||||
-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }
|
||||
|
||||
################################################################################
|
||||
# JNA
|
||||
################################################################################
|
||||
|
||||
@@ -31,8 +31,14 @@ interface TotpCodeManager {
|
||||
const val PERIOD_PARAM = "period"
|
||||
const val SECRET_PARAM = "secret"
|
||||
const val ISSUER_PARAM = "issuer"
|
||||
|
||||
/**
|
||||
* URI query parameter containing export data from Google Authenticator.
|
||||
*/
|
||||
const val DATA_PARAM = "data"
|
||||
const val TOTP_CODE_PREFIX = "otpauth://totp"
|
||||
const val STEAM_CODE_PREFIX = "steam://"
|
||||
const val GOOGLE_EXPORT_PREFIX = "otpauth-migration://"
|
||||
const val TOTP_DIGITS_DEFAULT = 6
|
||||
const val STEAM_DIGITS_DEFAULT = 5
|
||||
const val PERIOD_SECONDS_DEFAULT = 30
|
||||
|
||||
@@ -72,6 +72,11 @@ interface AuthenticatorRepository {
|
||||
*/
|
||||
suspend fun createItem(item: AuthenticatorItemEntity): CreateItemResult
|
||||
|
||||
/**
|
||||
* Attempt to add provided [items].
|
||||
*/
|
||||
suspend fun addItems(vararg items: AuthenticatorItemEntity): CreateItemResult
|
||||
|
||||
/**
|
||||
* Attempt to delete a cipher.
|
||||
*/
|
||||
|
||||
@@ -204,6 +204,15 @@ class AuthenticatorRepositoryImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun addItems(vararg items: AuthenticatorItemEntity): CreateItemResult {
|
||||
return try {
|
||||
authenticatorDiskSource.saveItem(*items)
|
||||
CreateItemResult.Success
|
||||
} catch (e: Exception) {
|
||||
CreateItemResult.Error
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun hardDeleteItem(itemId: String): DeleteItemResult {
|
||||
return try {
|
||||
authenticatorDiskSource.deleteItem(itemId)
|
||||
|
||||
@@ -6,9 +6,14 @@ package com.bitwarden.authenticator.data.authenticator.repository.model
|
||||
sealed class TotpCodeResult {
|
||||
|
||||
/**
|
||||
* Code has been successfully added.
|
||||
* Code containing an OTP URI has been successfully scanned.
|
||||
*/
|
||||
data class Success(val code: String) : TotpCodeResult()
|
||||
data class TotpCodeScan(val code: String) : TotpCodeResult()
|
||||
|
||||
/**
|
||||
* Code containing exported data from Google Authenticator was scanned.
|
||||
*/
|
||||
data class GoogleExportScan(val data: String) : TotpCodeResult()
|
||||
|
||||
/**
|
||||
* There was an error scanning the code.
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.bitwarden.authenticator.data.platform.manager
|
||||
|
||||
/**
|
||||
* An interface for encoding and decoding data.
|
||||
*/
|
||||
interface BitwardenEncodingManager {
|
||||
|
||||
/**
|
||||
* Decodes '%'-escaped octets in the given string.
|
||||
*/
|
||||
fun uriDecode(value: String): String
|
||||
|
||||
/**
|
||||
* Decodes the specified [value], and returns the resulting [ByteArray].
|
||||
*/
|
||||
fun base64Decode(value: String): ByteArray
|
||||
|
||||
/**
|
||||
* Encodes the specified [byteArray], and returns the encoded String.
|
||||
*/
|
||||
fun base32Encode(byteArray: ByteArray): String
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.bitwarden.authenticator.data.platform.manager
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.common.io.BaseEncoding
|
||||
|
||||
/**
|
||||
* Default implementation of [BitwardenEncodingManager].
|
||||
*/
|
||||
class BitwardenEncodingManagerImpl : BitwardenEncodingManager {
|
||||
override fun uriDecode(value: String): String = Uri.decode(value)
|
||||
|
||||
override fun base64Decode(value: String): ByteArray = BaseEncoding.base64().decode(value)
|
||||
|
||||
override fun base32Encode(byteArray: ByteArray): String =
|
||||
BaseEncoding.base32().encode(byteArray)
|
||||
}
|
||||
@@ -5,6 +5,8 @@ import com.bitwarden.authenticator.data.authenticator.datasource.disk.Authentica
|
||||
import com.bitwarden.authenticator.data.platform.datasource.disk.SettingsDiskSource
|
||||
import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.BiometricsEncryptionManagerImpl
|
||||
import com.bitwarden.authenticator.data.platform.manager.BitwardenEncodingManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.BitwardenEncodingManagerImpl
|
||||
import com.bitwarden.authenticator.data.platform.manager.CrashLogsManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.CrashLogsManagerImpl
|
||||
import com.bitwarden.authenticator.data.platform.manager.DispatcherManager
|
||||
@@ -67,4 +69,8 @@ object PlatformManagerModule {
|
||||
fun provideImportManager(
|
||||
authenticatorDiskSource: AuthenticatorDiskSource,
|
||||
): ImportManager = ImportManagerImpl(authenticatorDiskSource)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideEncodingManager(): BitwardenEncodingManager = BitwardenEncodingManagerImpl()
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class EditItemViewModel @Inject constructor(
|
||||
it.copy(
|
||||
dialog = EditItemState.DialogState.Generic(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.validation_field_required.asText(R.string.name),
|
||||
message = R.string.validation_field_required.asText(R.string.name.asText()),
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -97,7 +97,7 @@ class EditItemViewModel @Inject constructor(
|
||||
it.copy(
|
||||
dialog = EditItemState.DialogState.Generic(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.validation_field_required.asText(R.string.key),
|
||||
message = R.string.validation_field_required.asText(R.string.key.asText()),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -7,12 +7,15 @@ import com.bitwarden.authenticator.R
|
||||
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.TotpCodeManager
|
||||
import com.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult
|
||||
import com.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult
|
||||
import com.bitwarden.authenticator.data.platform.manager.BitwardenEncodingManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
|
||||
import com.bitwarden.authenticator.data.platform.manager.imports.model.GoogleAuthenticatorProtos
|
||||
import com.bitwarden.authenticator.data.platform.repository.SettingsRepository
|
||||
import com.bitwarden.authenticator.data.platform.repository.model.DataState
|
||||
import com.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem
|
||||
@@ -39,6 +42,7 @@ import javax.inject.Inject
|
||||
class ItemListingViewModel @Inject constructor(
|
||||
private val authenticatorRepository: AuthenticatorRepository,
|
||||
private val clipboardManager: BitwardenClipboardManager,
|
||||
private val encodingManager: BitwardenEncodingManager,
|
||||
settingsRepository: SettingsRepository,
|
||||
) : BaseViewModel<ItemListingState, ItemListingEvent, ItemListingAction>(
|
||||
initialState = ItemListingState(
|
||||
@@ -261,32 +265,120 @@ class ItemListingViewModel @Inject constructor(
|
||||
viewModelScope.launch {
|
||||
when (val totpResult = action.totpResult) {
|
||||
TotpCodeResult.CodeScanningError -> {
|
||||
sendAction(
|
||||
action = ItemListingAction.Internal.CreateItemResultReceive(
|
||||
result = CreateItemResult.Error,
|
||||
),
|
||||
)
|
||||
handleCodeScanningErrorReceive()
|
||||
}
|
||||
|
||||
is TotpCodeResult.Success -> {
|
||||
is TotpCodeResult.TotpCodeScan -> {
|
||||
handleTotpCodeScanReceive(totpResult)
|
||||
}
|
||||
|
||||
val item = totpResult.code.toAuthenticatorEntityOrNull()
|
||||
?: run {
|
||||
sendAction(
|
||||
action = ItemListingAction.Internal.CreateItemResultReceive(
|
||||
result = CreateItemResult.Error,
|
||||
),
|
||||
)
|
||||
return@launch
|
||||
}
|
||||
|
||||
val result = authenticatorRepository.createItem(item)
|
||||
sendAction(ItemListingAction.Internal.CreateItemResultReceive(result))
|
||||
is TotpCodeResult.GoogleExportScan -> {
|
||||
handleGoogleExportScan(totpResult)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun handleTotpCodeScanReceive(
|
||||
totpResult: TotpCodeResult.TotpCodeScan,
|
||||
) {
|
||||
val item = totpResult.code.toAuthenticatorEntityOrNull()
|
||||
?: run {
|
||||
handleCodeScanningErrorReceive()
|
||||
return
|
||||
}
|
||||
|
||||
val result = authenticatorRepository.createItem(item)
|
||||
sendAction(ItemListingAction.Internal.CreateItemResultReceive(result))
|
||||
}
|
||||
|
||||
private suspend fun handleGoogleExportScan(
|
||||
totpResult: TotpCodeResult.GoogleExportScan,
|
||||
) {
|
||||
val base64EncodedMigrationData = encodingManager.uriDecode(
|
||||
value = totpResult.data,
|
||||
)
|
||||
|
||||
val decodedMigrationData = encodingManager.base64Decode(
|
||||
value = base64EncodedMigrationData,
|
||||
)
|
||||
|
||||
val payload = GoogleAuthenticatorProtos.MigrationPayload
|
||||
.parseFrom(decodedMigrationData)
|
||||
|
||||
val entries = payload
|
||||
.otpParametersList
|
||||
.mapNotNull { otpParam ->
|
||||
val secret = encodingManager.base32Encode(
|
||||
byteArray = otpParam.secret.toByteArray()
|
||||
)
|
||||
|
||||
// Google Authenticator only supports TOTP and HOTP codes. We do not support HOTP
|
||||
// codes so we skip over codes that are not TOTP.
|
||||
val type = when (otpParam.type) {
|
||||
GoogleAuthenticatorProtos.MigrationPayload.OtpType.OTP_TOTP -> {
|
||||
AuthenticatorItemType.TOTP
|
||||
}
|
||||
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
|
||||
// Google Authenticator does not always provide a valid digits value so we double
|
||||
// check it and fallback to the default value if it is not within our valid range.
|
||||
val digits = if (otpParam.digits in 5..10) {
|
||||
otpParam.digits
|
||||
} else {
|
||||
TotpCodeManager.TOTP_DIGITS_DEFAULT
|
||||
}
|
||||
|
||||
// Google Authenticator only supports SHA1 algorithms.
|
||||
val algorithm = AuthenticatorItemAlgorithm.SHA1
|
||||
|
||||
// Google Authenticator ignores period so we always set it to our default.
|
||||
val period = TotpCodeManager.PERIOD_SECONDS_DEFAULT
|
||||
|
||||
val accountName: String = when {
|
||||
otpParam.issuer.isNullOrEmpty().not() &&
|
||||
otpParam.name.startsWith("${otpParam.issuer}:") -> {
|
||||
otpParam.name.replace("${otpParam.issuer}:", "")
|
||||
}
|
||||
|
||||
else -> otpParam.name
|
||||
}
|
||||
|
||||
// If the issuer is not provided fallback to the token name since issuer is required
|
||||
// in our database
|
||||
val issuer = when {
|
||||
otpParam.issuer.isNullOrEmpty() -> otpParam.name
|
||||
else -> otpParam.issuer
|
||||
}
|
||||
|
||||
AuthenticatorItemEntity(
|
||||
id = UUID.randomUUID().toString(),
|
||||
key = secret,
|
||||
type = type,
|
||||
algorithm = algorithm,
|
||||
period = period,
|
||||
digits = digits,
|
||||
issuer = issuer,
|
||||
accountName = accountName,
|
||||
userId = null,
|
||||
favorite = false,
|
||||
)
|
||||
}
|
||||
|
||||
val result = authenticatorRepository.addItems(*entries.toTypedArray())
|
||||
sendAction(ItemListingAction.Internal.CreateItemResultReceive(result))
|
||||
}
|
||||
|
||||
private suspend fun handleCodeScanningErrorReceive() {
|
||||
sendAction(
|
||||
action = ItemListingAction.Internal.CreateItemResultReceive(
|
||||
result = CreateItemResult.Error,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAlertThresholdSecondsReceive(
|
||||
action: ItemListingAction.Internal.AlertThresholdSecondsReceive,
|
||||
) {
|
||||
|
||||
@@ -43,17 +43,22 @@ class QrCodeScanViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
|
||||
// For more information: https://bitwarden.com/help/authenticator-keys/#support-for-more-parameters
|
||||
private fun handleQrCodeScanReceive(action: QrCodeScanAction.QrCodeScanReceive) {
|
||||
var result: TotpCodeResult = TotpCodeResult.Success(action.qrCode)
|
||||
val scannedCode = action.qrCode
|
||||
|
||||
if (scannedCode.isBlank() || !scannedCode.startsWith(TotpCodeManager.TOTP_CODE_PREFIX)) {
|
||||
if (scannedCode.startsWith(TotpCodeManager.TOTP_CODE_PREFIX)) {
|
||||
handleTotpUriReceive(scannedCode)
|
||||
} else if (scannedCode.startsWith(TotpCodeManager.GOOGLE_EXPORT_PREFIX)) {
|
||||
handleGoogleExportUriReceive(scannedCode)
|
||||
} else {
|
||||
authenticatorRepository.emitTotpCodeResult(TotpCodeResult.CodeScanningError)
|
||||
sendEvent(QrCodeScanEvent.NavigateBack)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// For more information: https://bitwarden.com/help/authenticator-keys/#support-for-more-parameters
|
||||
private fun handleTotpUriReceive(scannedCode: String) {
|
||||
var result: TotpCodeResult = TotpCodeResult.TotpCodeScan(scannedCode)
|
||||
val scannedCodeUri = Uri.parse(scannedCode)
|
||||
val secretValue = scannedCodeUri
|
||||
.getQueryParameter(TotpCodeManager.SECRET_PARAM)
|
||||
@@ -70,7 +75,18 @@ class QrCodeScanViewModel @Inject constructor(
|
||||
if (!areParametersValid(scannedCode, values)) {
|
||||
result = TotpCodeResult.CodeScanningError
|
||||
}
|
||||
authenticatorRepository.emitTotpCodeResult(result)
|
||||
sendEvent(QrCodeScanEvent.NavigateBack)
|
||||
}
|
||||
|
||||
private fun handleGoogleExportUriReceive(scannedCode: String) {
|
||||
val uri = Uri.parse(scannedCode)
|
||||
val encodedData = uri.getQueryParameter(TotpCodeManager.DATA_PARAM)
|
||||
val result: TotpCodeResult = if (encodedData.isNullOrEmpty()) {
|
||||
TotpCodeResult.CodeScanningError
|
||||
} else {
|
||||
TotpCodeResult.GoogleExportScan(encodedData)
|
||||
}
|
||||
authenticatorRepository.emitTotpCodeResult(result)
|
||||
sendEvent(QrCodeScanEvent.NavigateBack)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
||||
|
||||
val source = PlanarYUVLuminanceSource(
|
||||
image.planes[0].buffer.toByteArray(),
|
||||
image.width,
|
||||
image.planes[0].rowStride,
|
||||
image.height,
|
||||
0,
|
||||
0,
|
||||
@@ -39,24 +39,22 @@ class QrCodeAnalyzerImpl : QrCodeAnalyzer {
|
||||
image.height,
|
||||
false,
|
||||
)
|
||||
|
||||
val binaryBitmap = BinaryBitmap(HybridBinarizer(source))
|
||||
try {
|
||||
val result = MultiFormatReader()
|
||||
.apply {
|
||||
setHints(
|
||||
mapOf(
|
||||
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(
|
||||
BarcodeFormat.QR_CODE,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
.decode(binaryBitmap)
|
||||
val result = MultiFormatReader().decode(
|
||||
/* image = */ binaryBitmap,
|
||||
/* hints = */
|
||||
mapOf(
|
||||
DecodeHintType.POSSIBLE_FORMATS to arrayListOf(BarcodeFormat.QR_CODE),
|
||||
DecodeHintType.ALSO_INVERTED to true
|
||||
),
|
||||
)
|
||||
|
||||
qrCodeRead = true
|
||||
onQrCodeScanned(result.text)
|
||||
} catch (e: NotFoundException) {
|
||||
return
|
||||
} catch (ignored: NotFoundException) {
|
||||
|
||||
} finally {
|
||||
image.close()
|
||||
}
|
||||
|
||||
33
app/src/main/proto/google_authenticator.proto
Normal file
33
app/src/main/proto/google_authenticator.proto
Normal file
@@ -0,0 +1,33 @@
|
||||
syntax = "proto3";
|
||||
|
||||
option java_package = "com.bitwarden.authenticator.data.platform.manager.imports.model";
|
||||
option java_outer_classname = "GoogleAuthenticatorProtos";
|
||||
|
||||
message MigrationPayload {
|
||||
enum Algorithm {
|
||||
ALGO_INVALID = 0;
|
||||
ALGO_SHA1 = 1;
|
||||
}
|
||||
|
||||
enum OtpType {
|
||||
OTP_INVALID = 0;
|
||||
OTP_HOTP = 1;
|
||||
OTP_TOTP = 2;
|
||||
}
|
||||
|
||||
message OtpParameters {
|
||||
bytes secret = 1;
|
||||
string name = 2;
|
||||
string issuer = 3;
|
||||
Algorithm algorithm = 4;
|
||||
int32 digits = 5;
|
||||
OtpType type = 6;
|
||||
int64 counter = 7;
|
||||
}
|
||||
|
||||
repeated OtpParameters otp_parameters = 1;
|
||||
int32 version = 2;
|
||||
int32 batch_size = 3;
|
||||
int32 batch_index = 4;
|
||||
int32 batch_id = 5;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package com.bitwarden.android.authenticator
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
alias(libs.plugins.android.application) apply false
|
||||
alias(libs.plugins.google.protobuf) apply false
|
||||
alias(libs.plugins.google.services) apply false
|
||||
alias(libs.plugins.hilt) apply false
|
||||
alias(libs.plugins.kotlin.android) apply false
|
||||
alias(libs.plugins.kotlin.parcelize) apply false
|
||||
alias(libs.plugins.kotlinx.kover) apply false
|
||||
alias(libs.plugins.ksp) apply false
|
||||
alias(libs.plugins.google.services) apply false
|
||||
alias(libs.plugins.sonarqube) apply false
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ espresso = "3.5.1"
|
||||
fastlaneScreengrab = "2.1.1"
|
||||
firebaseBom = "33.0.0"
|
||||
glide = "1.0.0-beta01"
|
||||
googleGuava = "33.0.0-android"
|
||||
googleProtoBufJava = "3.25.1"
|
||||
googleProtoBufPlugin = "0.9.4"
|
||||
googleServices = "4.4.1"
|
||||
hilt = "2.51.1"
|
||||
junit5 = "5.10.2"
|
||||
@@ -94,20 +97,24 @@ androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", vers
|
||||
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "androidxWork" }
|
||||
bitwarden-sdk = { module = "com.bitwarden:sdk-android", version.ref = "bitwardenSdk" }
|
||||
bumptech-glide = { module = "com.github.bumptech.glide:compose", version.ref = "glide" }
|
||||
fastlane-screengrab = { module = "tools.fastlane:screengrab", version.ref = "fastlaneScreengrab"}
|
||||
fastlane-screengrab = { module = "tools.fastlane:screengrab", version.ref = "fastlaneScreengrab" }
|
||||
google-firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" }
|
||||
google-firebase-cloud-messaging = { module = "com.google.firebase:firebase-messaging-ktx" }
|
||||
google-firebase-crashlytics = { module = "com.google.firebase:firebase-crashlytics-ktx" }
|
||||
google-guava = { module = "com.google.guava:guava", version.ref = "googleGuava" }
|
||||
google-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" }
|
||||
google-hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hilt" }
|
||||
google-hilt-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" }
|
||||
google-protobuf-javalite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "googleProtoBufJava" }
|
||||
# Included so that Renovate tracks updates since protoc is not referenced directly in `dependency {}` blocks.
|
||||
google-protobuf-protoc = { module = "com.google.protobuf:protoc", version.ref = "googleProtoBufJava" }
|
||||
jakewharton-retrofit-kotlinx-serialization = { module = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter", version.ref = "retrofitKotlinxSerialization" }
|
||||
junit-junit5 = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit5" }
|
||||
junit-vintage = { module = "org.junit.vintage:junit-vintage-engine", version.ref = "junit5" }
|
||||
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
|
||||
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
|
||||
kotlinx-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerialization" }
|
||||
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" }
|
||||
mockk-mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||
robolectric-robolectric = { module = "org.robolectric:robolectric", version.ref = "roboelectric" }
|
||||
@@ -121,6 +128,7 @@ zxing-zxing-core = { module = "com.google.zxing:core", version.ref = "zxing" }
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
|
||||
crashlytics = { id = "com.google.firebase.crashlytics", version.ref = "crashlytics" }
|
||||
google-protobuf = { id = "com.google.protobuf", version.ref = "googleProtoBufPlugin" }
|
||||
google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" }
|
||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||
|
||||
Reference in New Issue
Block a user