Skip to content
Open
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 @@ -8,17 +8,17 @@
package com.pingidentity.storage.sqlite

import android.content.Context
import android.database.Cursor
import com.pingidentity.logger.Logger
import com.pingidentity.storage.exception.StorageException
import com.pingidentity.storage.sqlite.passphrase.KeyStorePassphraseProvider
import com.pingidentity.storage.sqlite.passphrase.PassphraseProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import net.sqlcipher.Cursor
import net.sqlcipher.database.SQLiteDatabase
import net.sqlcipher.database.SQLiteOpenHelper
import kotlin.coroutines.coroutineContext
import net.zetetic.database.sqlcipher.SQLiteDatabase
import net.zetetic.database.sqlcipher.SQLiteOpenHelper

/**
* Base implementation for SQLite storage that uses SQLCipher for encrypted storage.
Expand Down Expand Up @@ -63,7 +63,7 @@ open class SQLiteStorage(
suspend fun initializeDatabase() = executeOnIO {
try {
// Load SQLCipher libraries
SQLiteDatabase.loadLibs(context)
System.loadLibrary("sqlcipher")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is as part of the migration suggested by the sqlcipher library.


// Create database helper
dbHelper = createDatabaseHelper(context, databaseName, databaseVersion)
Expand All @@ -74,17 +74,22 @@ open class SQLiteStorage(

try {
// Open or create the database
internalDatabase = dbHelper.getWritableDatabase(passphrase)
internalDatabase = SQLiteDatabase.openOrCreateDatabase(
context.getDatabasePath(databaseName).absolutePath,
passphrase,
null,
null,
)
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()

// If opening fails, try to recover
logger.e("Database initialization failed: ${e.message}", e)
closeAndCleanupDatabase()

// Create a new helper and try again
dbHelper = createDatabaseHelper(context, databaseName, databaseVersion)
internalDatabase = dbHelper.getWritableDatabase(passphrase)
internalDatabase = dbHelper.writableDatabase
}

logger.d("SQL storage initialized successfully")
Expand All @@ -97,12 +102,12 @@ open class SQLiteStorage(
try {
creator(internalDatabase)
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to create table: ${e.message}", e)
}
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to initialize database: ${e.message}", e)
throw StorageException("Failed to initialize database", e)
}
Expand Down Expand Up @@ -143,7 +148,7 @@ open class SQLiteStorage(
}
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Database validation query failed: ${e.message}", e)
closeAndCleanupDatabase()
throw e
Expand All @@ -162,7 +167,7 @@ open class SQLiteStorage(
internalDatabase.use { /* it.close() is called automatically */ }
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Error during internalDatabase.close(): ${e.message}", e)
}

Expand All @@ -172,7 +177,7 @@ open class SQLiteStorage(
dbHelper.close()
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Error during dbHelper.close(): ${e.message}", e)
}

Expand All @@ -181,7 +186,7 @@ open class SQLiteStorage(
context.deleteDatabase(databaseName)
logger.d("Database file deleted successfully")
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to delete database file: ${e.message}", e)
}

Expand All @@ -200,10 +205,10 @@ open class SQLiteStorage(
// Create temporary instances to satisfy lateinit requirements
dbHelper = createDatabaseHelper(context, databaseName, databaseVersion)
try {
internalDatabase = dbHelper.getReadableDatabase("")
internalDatabase = dbHelper.readableDatabase
internalDatabase.close()
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to create temporary database instance: ${e.message}", e)
}
}
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtim
androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" }
androidx-ui-graphics = { module = "androidx.compose.ui:ui-graphics" }
androidx-sqlite = { group = "androidx.sqlite", name = "sqlite", version = "2.5.2" }
sqlcipher = { group = "net.zetetic", name = "android-database-sqlcipher", version = "4.5.4" }
sqlcipher = { group = "net.zetetic", name = "sqlcipher-android", version = "4.12.0" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This version change is the primary change which updates the library to be 16KB compatible.

commons-codec = { group = "commons-codec", name = "commons-codec", version = "1.15" }
barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version.ref = "barcodeScanning" }
play-services-auth-blockstore = { module = "com.google.android.gms:play-services-auth-blockstore", version.ref = "playServicesAuthBlockstore" }
Expand Down
6 changes: 6 additions & 0 deletions mfa/oath/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ android {
enableUnitTestCoverage = true
}
}

packaging {
jniLibs {
pickFirsts.add("**/*.so")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was added because of a duplicate dependency being added which fails the build.
The file that was causing the issue: forgerock-core-4.8.0/jni/arm64-v8a/libtool-file.so

}
}
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,21 @@
package com.pingidentity.mfa.oath.storage

import android.content.Context
import android.database.Cursor
import com.pingidentity.android.ContextProvider
import com.pingidentity.logger.Logger
import com.pingidentity.mfa.commons.exception.MfaStorageException
import com.pingidentity.mfa.oath.OathAlgorithm
import com.pingidentity.mfa.oath.OathCredential
import com.pingidentity.mfa.oath.storage.OathStorage
import com.pingidentity.mfa.oath.OathType
import com.pingidentity.storage.sqlite.passphrase.KeyStorePassphraseProvider
import com.pingidentity.storage.sqlite.passphrase.PassphraseProvider
import com.pingidentity.storage.sqlite.SQLiteStorage
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.currentCoroutineContext
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.withContext
import net.sqlcipher.Cursor
import java.util.Date
import kotlin.coroutines.coroutineContext

/**
* SQLite-based implementation of [OathStorage].
Expand Down Expand Up @@ -150,7 +149,7 @@ class SQLOathStorage private constructor(
initializeDatabase()
logger.d("OATH storage initialized")
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
throw MfaStorageException("Failed to initialize OATH storage", e)
}
}
Expand All @@ -166,7 +165,7 @@ class SQLOathStorage private constructor(
clearOathCredentials()
logger.d("Cleared all data from OATH storage")
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to clear OATH storage: ${e.message}", e)
throw MfaStorageException("Failed to clear OATH storage", e)
}
Expand All @@ -181,7 +180,7 @@ class SQLOathStorage private constructor(
closeDatabase()
logger.d("OATH SQL storage closed")
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Error closing OATH storage: ${e.message}", e)
}
}
Expand Down Expand Up @@ -221,7 +220,7 @@ class SQLOathStorage private constructor(

storeOathCredentialData(credential.id, data)
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
throw MfaStorageException("Failed to store OATH credential with ID ${credential.id}", e)
}
}
Expand Down Expand Up @@ -272,7 +271,7 @@ class SQLOathStorage private constructor(
endTransaction()
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to store OATH credential with ID $credentialId: ${e.message}", e)
throw MfaStorageException("Failed to store OATH credential with ID $credentialId", e)
}
Expand All @@ -290,7 +289,7 @@ class SQLOathStorage private constructor(
val data = retrieveOathCredentialData(credentialId) ?: return null
return createOathCredentialFromData(data)
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
throw MfaStorageException("Failed to retrieve OATH credential with ID $credentialId", e)
}
}
Expand All @@ -316,7 +315,7 @@ class SQLOathStorage private constructor(

return@withContext results.firstOrNull()
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to retrieve OATH credential with ID $credentialId: ${e.message}", e)
throw MfaStorageException("Failed to retrieve OATH credential with ID $credentialId", e)
}
Expand All @@ -333,7 +332,7 @@ class SQLOathStorage private constructor(
val dataList = retrieveAllOathCredentialsData()
return dataList.mapNotNull { createOathCredentialFromData(it) }
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
throw MfaStorageException("Failed to retrieve all OATH credentials", e)
}
}
Expand All @@ -356,7 +355,7 @@ class SQLOathStorage private constructor(
extractDataFromCursor(cursor)
}
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to retrieve all OATH credentials: ${e.message}", e)
throw MfaStorageException("Failed to retrieve all OATH credentials", e)
}
Expand All @@ -373,7 +372,7 @@ class SQLOathStorage private constructor(
try {
return deleteOathCredential(credentialId)
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
throw MfaStorageException("Failed to remove OATH credential with ID $credentialId", e)
}
}
Expand Down Expand Up @@ -404,7 +403,7 @@ class SQLOathStorage private constructor(

return@withContext wasDeleted
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to delete OATH credential with ID $credentialId: ${e.message}", e)
throw MfaStorageException("Failed to delete OATH credential with ID $credentialId", e)
}
Expand All @@ -419,7 +418,7 @@ class SQLOathStorage private constructor(
try {
clearAllOathCredentials()
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
throw MfaStorageException("Failed to clear OATH credentials", e)
}
}
Expand All @@ -436,7 +435,7 @@ class SQLOathStorage private constructor(
database.delete(OATH_TABLE, null, null)
logger.d("Cleared all OATH credentials")
} catch (e: Exception) {
coroutineContext.ensureActive()
currentCoroutineContext().ensureActive()
logger.e("Failed to clear OATH credentials: ${e.message}", e)
throw MfaStorageException("Failed to clear OATH credentials", e)
}
Expand Down
Loading
Loading