-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #96 from SecUSo/feat/add-privacy-friendly-backup-s…
…upport Add privacy friendly backup support
- Loading branch information
Showing
70 changed files
with
625 additions
and
364 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[submodule "libs/privacy-friendly-backup-api"] | ||
path = libs/privacy-friendly-backup-api | ||
url = https://github.com/SecUSo/privacy-friendly-backup-api.git |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
21 changes: 21 additions & 0 deletions
21
app/src/main/java/org/secuso/privacyfriendlyfoodtracker/PFFoodTrackerApplication.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package org.secuso.privacyfriendlyfoodtracker | ||
|
||
import android.app.Application | ||
import android.util.Log | ||
import androidx.work.Configuration | ||
import org.secuso.privacyfriendlybackup.api.pfa.BackupManager | ||
import org.secuso.privacyfriendlyfoodtracker.backup.BackupCreator | ||
import org.secuso.privacyfriendlyfoodtracker.backup.BackupRestorer | ||
|
||
class PFFoodTrackerApplication : Application(), Configuration.Provider { | ||
|
||
override fun onCreate() { | ||
super.onCreate() | ||
BackupManager.backupCreator = BackupCreator() | ||
BackupManager.backupRestorer = BackupRestorer() | ||
} | ||
|
||
override fun getWorkManagerConfiguration(): Configuration { | ||
return Configuration.Builder().setMinimumLoggingLevel(Log.INFO).build() | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
app/src/main/java/org/secuso/privacyfriendlyfoodtracker/backup/BackupCreator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
package org.secuso.privacyfriendlyfoodtracker.backup | ||
|
||
import android.content.Context | ||
import android.preference.PreferenceManager | ||
import android.util.JsonWriter | ||
import android.util.Log | ||
import net.sqlcipher.database.SQLiteDatabase | ||
import net.sqlcipher.database.SQLiteDatabaseHook | ||
import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil | ||
import org.secuso.privacyfriendlybackup.api.backup.PreferenceUtil | ||
import org.secuso.privacyfriendlybackup.api.pfa.IBackupCreator | ||
import org.secuso.privacyfriendlyfoodtracker.database.ApplicationDatabase | ||
import org.secuso.privacyfriendlyfoodtracker.helpers.KeyGenHelper | ||
import java.io.OutputStream | ||
import java.io.OutputStreamWriter | ||
|
||
class BackupCreator : IBackupCreator { | ||
override fun writeBackup(context: Context, outputStream: OutputStream) { | ||
Log.d(TAG, "createBackup() started") | ||
val outputStreamWriter = OutputStreamWriter(outputStream, Charsets.UTF_8) | ||
val writer = JsonWriter(outputStreamWriter) | ||
writer.setIndent("") | ||
|
||
try { | ||
writer.beginObject() | ||
|
||
Log.d(TAG, "Writing database") | ||
writer.name("database") | ||
|
||
SQLiteDatabase.loadLibs(context) | ||
|
||
if (context.getDatabasePath(ApplicationDatabase.DATABASE_NAME).exists()) { | ||
val database = SQLiteDatabase.openDatabase( | ||
context.getDatabasePath(ApplicationDatabase.DATABASE_NAME).path, | ||
KeyGenHelper.getSecretKeyAsChar(context), | ||
null, | ||
SQLiteDatabase.OPEN_READONLY, | ||
object : SQLiteDatabaseHook { | ||
override fun preKey(database: SQLiteDatabase) {} | ||
override fun postKey(database: SQLiteDatabase) { | ||
database.rawExecSQL("PRAGMA cipher_compatibility = 3;") | ||
} | ||
}) | ||
|
||
DatabaseUtil.writeDatabase(writer, database) | ||
database.close() | ||
} else { | ||
Log.d(TAG, "No database found") | ||
writer.beginObject() | ||
writer.endObject() | ||
} | ||
|
||
Log.d(TAG, "Writing preferences") | ||
writer.name("preferences") | ||
|
||
val pref = PreferenceManager.getDefaultSharedPreferences(context.applicationContext) | ||
PreferenceUtil.writePreferences(writer, pref, arrayOf(KeyGenHelper.PREFERENCE_ENCRYPTED_KEY_NAME)) | ||
|
||
Log.d(TAG, "Writing files") | ||
writer.endObject() | ||
writer.close() | ||
} catch (e: Exception) { | ||
Log.e(TAG, "Error occurred", e) | ||
e.printStackTrace() | ||
} | ||
|
||
Log.d(TAG, "Backup created successfully") | ||
} | ||
|
||
companion object { | ||
const val TAG = "PFABackupCreator" | ||
} | ||
} |
126 changes: 126 additions & 0 deletions
126
app/src/main/java/org/secuso/privacyfriendlyfoodtracker/backup/BackupRestorer.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package org.secuso.privacyfriendlyfoodtracker.backup | ||
|
||
import android.content.Context | ||
import android.content.SharedPreferences | ||
import android.preference.PreferenceManager | ||
import android.util.JsonReader | ||
import android.util.Log | ||
import androidx.annotation.NonNull | ||
import net.sqlcipher.database.SQLiteDatabase | ||
import net.sqlcipher.database.SQLiteDatabaseHook | ||
import org.secuso.privacyfriendlybackup.api.backup.DatabaseUtil | ||
import org.secuso.privacyfriendlybackup.api.backup.FileUtil | ||
import org.secuso.privacyfriendlybackup.api.pfa.IBackupRestorer | ||
import org.secuso.privacyfriendlyfoodtracker.database.ApplicationDatabase | ||
import org.secuso.privacyfriendlyfoodtracker.helpers.KeyGenHelper | ||
import java.io.IOException | ||
import java.io.InputStream | ||
import java.io.InputStreamReader | ||
import kotlin.system.exitProcess | ||
|
||
|
||
class BackupRestorer : IBackupRestorer { | ||
@Throws(IOException::class) | ||
private fun readDatabase(@NonNull reader: JsonReader, @NonNull context: Context) { | ||
reader.beginObject() | ||
val n1: String = reader.nextName() | ||
if (n1 != "version") { | ||
throw RuntimeException("Unknown value $n1") | ||
} | ||
val version: Int = reader.nextInt() | ||
val n2: String = reader.nextName() | ||
if (n2 != "content") { | ||
throw RuntimeException("Unknown value $n2") | ||
} | ||
|
||
Log.d(TAG, "Restoring database...") | ||
val restoreDatabaseName = "restoreDatabase" | ||
|
||
// delete if file already exists | ||
val restoreDatabaseFile = context.getDatabasePath(restoreDatabaseName) | ||
if (restoreDatabaseFile.exists()) { | ||
DatabaseUtil.deleteRoomDatabase(context, restoreDatabaseName) | ||
} | ||
|
||
// check if key exists | ||
if (!KeyGenHelper.isKeyGenerated()) { | ||
Log.d(TAG, "No key found. Generating new key...") | ||
KeyGenHelper.generateKey(context) | ||
KeyGenHelper.generatePassphrase(context) | ||
Log.d(TAG, "Key generated") | ||
} | ||
val databasePassword = KeyGenHelper.getSecretKeyAsChar(context) | ||
// create new restore database | ||
val db: SQLiteDatabase = | ||
SQLiteDatabase.openOrCreateDatabase(context.getDatabasePath(restoreDatabaseName).path, databasePassword, null, object : SQLiteDatabaseHook { | ||
override fun preKey(database: SQLiteDatabase?) {} | ||
override fun postKey(database: SQLiteDatabase) { | ||
database.rawExecSQL("PRAGMA cipher_compatibility = 3;") | ||
} | ||
}) | ||
|
||
db.beginTransaction() | ||
db.version = version | ||
|
||
Log.d(TAG, "Copying database contents...") | ||
DatabaseUtil.readDatabaseContent(reader, db) | ||
db.setTransactionSuccessful() | ||
db.endTransaction() | ||
db.close() | ||
|
||
reader.endObject() | ||
|
||
// copy file to correct location | ||
val actualDatabaseFile = context.getDatabasePath(ApplicationDatabase.DATABASE_NAME) | ||
|
||
DatabaseUtil.deleteRoomDatabase(context, ApplicationDatabase.DATABASE_NAME) | ||
|
||
FileUtil.copyFile(restoreDatabaseFile, actualDatabaseFile) | ||
Log.d(TAG, "Database Restored") | ||
|
||
// delete restore database | ||
DatabaseUtil.deleteRoomDatabase(context, restoreDatabaseName) | ||
} | ||
|
||
@Throws(IOException::class) | ||
private fun readPreferences(@NonNull reader: JsonReader, @NonNull context: Context) { | ||
reader.beginObject() | ||
val pref: SharedPreferences.Editor = PreferenceManager.getDefaultSharedPreferences(context).edit() | ||
while (reader.hasNext()) { | ||
val name: String = reader.nextName() | ||
when (name) { | ||
"IsFirstTimeLaunch" -> pref | ||
.putBoolean(name, reader.nextBoolean()) | ||
else -> throw RuntimeException("Unknown preference $name") | ||
} | ||
} | ||
pref.commit() | ||
reader.endObject() | ||
} | ||
|
||
override fun restoreBackup(context: Context, restoreData: InputStream): Boolean { | ||
return try { | ||
val isReader = InputStreamReader(restoreData) | ||
val reader = JsonReader(isReader) | ||
|
||
// START | ||
reader.beginObject() | ||
while (reader.hasNext()) { | ||
val type: String = reader.nextName() | ||
when (type) { | ||
"database" -> readDatabase(reader, context) | ||
"preferences" -> readPreferences(reader, context) | ||
else -> throw RuntimeException("Can not parse type $type") | ||
} | ||
} | ||
reader.endObject() | ||
exitProcess(0) | ||
} catch (e: Exception) { | ||
false | ||
} | ||
} | ||
|
||
companion object { | ||
const val TAG = "PFABackupRestorer" | ||
} | ||
} |
Oops, something went wrong.