Skip to content

Commit

Permalink
Merge pull request #96 from SecUSo/feat/add-privacy-friendly-backup-s…
Browse files Browse the repository at this point in the history
…upport

Add privacy friendly backup support
  • Loading branch information
udenr authored Sep 6, 2022
2 parents 2e4d1b5 + ba5e6d6 commit 0a7cd4c
Show file tree
Hide file tree
Showing 70 changed files with 625 additions and 364 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
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
64 changes: 38 additions & 26 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

android {
compileSdkVersion 28
buildToolsVersion '28.0.3'
compileSdkVersion 32

defaultConfig {
applicationId "org.secuso.privacyfriendlyfoodtracker"
minSdkVersion 21
targetSdkVersion 27
targetSdkVersion 32
versionCode 4
versionName "1.1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]
}
}
}

compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

buildTypes {
release {
minifyEnabled false
Expand All @@ -31,46 +38,51 @@ android {
}

dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test:rules:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'

androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'

implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.1'

coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'

// Room components
implementation "android.arch.persistence.room:runtime:$rootProject.roomVersion"
annotationProcessor "android.arch.persistence.room:compiler:$rootProject.roomVersion"
androidTestImplementation "android.arch.persistence.room:testing:$rootProject.roomVersion"
implementation 'androidx.room:room-runtime:2.4.3'
annotationProcessor 'androidx.room:room-compiler:2.4.3'
androidTestImplementation 'androidx.room:room-testing:2.4.3'

// Lifecycle components
implementation "android.arch.lifecycle:extensions:$rootProject.archLifecycleVersion"
annotationProcessor "android.arch.lifecycle:compiler:$rootProject.archLifecycleVersion"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

// Gson
implementation 'com.google.code.gson:gson:2.8.0'
implementation 'com.google.code.gson:gson:2.8.9'

// Saferoom
implementation "com.commonsware.cwac:saferoom:0.4.4"
implementation 'com.android.support:cardview-v7:28.0.0'
// SQLCipher
implementation 'net.zetetic:android-database-sqlcipher:4.5.0'

// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

//Jakewharton
implementation 'com.jakewharton.picasso:picasso2-okhttp3-downloader:1.1.0'

// Statistic
implementation 'com.jjoe64:graphview:4.2.2'

implementation 'com.android.support:recyclerview-v7:28.0.0'


// Backup API
implementation project(':backup-api')
def work_version = '2.7.1'
implementation "androidx.work:work-runtime:$work_version"
implementation "androidx.work:work-runtime-ktx:$work_version"
androidTestImplementation "androidx.work:work-testing:$work_version"

}
25 changes: 21 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.secuso.privacyfriendlyfoodtracker">

<supports-screens
Expand All @@ -13,18 +14,19 @@
<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".PFFoodTrackerApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ui.SplashActivity"
android:exported="true"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Expand Down Expand Up @@ -74,13 +76,28 @@
android:name=".ui.BaseStatisticActivity"
android:label="@string/action_statistic"
android:parentActivityName=".ui.MainActivity"
android:theme="@style/AppTheme.NoActionBar"></activity>
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".ui.BaseAddFoodActivity"
android:label="@string/title_activity_food"
android:parentActivityName=".ui.OverviewActivity"
android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="adjustResize"></activity>
android:windowSoftInputMode="adjustResize" />

<service
android:name=".backup.PFABackupService"
android:enabled="true"
android:exported="true"
android:process=":backup"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="org.secuso.privacyfriendlybackup.api.pfa.PFAAuthService" />
</intent-filter>
</service>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
</application>

</manifest>
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()
}
}
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"
}
}
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"
}
}
Loading

0 comments on commit 0a7cd4c

Please sign in to comment.