Skip to content

Commit

Permalink
Feature/issue 42 local storage encryption (#47)
Browse files Browse the repository at this point in the history
# *Feature/issue 42 local storage encryption*

## ♻️ Current situation & Problem
#42 


## ⚙️ Release Notes 

- Added Key-Value Storage (EncryptedFileStorage)
- File Storage (EncryptedSharedPreferencesStorage as well as
LocalStorage)

## 📝 Code of Conduct & Contributing Guidelines 

By submitting creating this pull request, you agree to follow our [Code
of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md):
- [x] I agree to follow the [Code of
Conduct](https://github.com/StanfordSpezi/.github/blob/main/CODE_OF_CONDUCT.md)
and [Contributing
Guidelines](https://github.com/StanfordSpezi/.github/blob/main/CONTRIBUTING.md).

---------

Signed-off-by: Basler182 <[email protected]>
  • Loading branch information
Basler182 authored Jun 23, 2024
1 parent 9c33498 commit 19b0573
Show file tree
Hide file tree
Showing 15 changed files with 673 additions and 2 deletions.
10 changes: 8 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ compileSdk = "34"
composeBom = "2024.05.00"
composeNavigation = "2.8.0-alpha08"
coreKtx = "1.13.1"
coreKtxVersion = "1.5.0"
coreTestingVersion = "2.2.0"
coroutinesVersion = "1.8.0"
credentialsPlayServicesAuth = "1.2.2"
datastorePreferences = "1.1.1"
detekt = "1.23.6" # please adjust github action version as well in case of version change
dokka = "1.9.20"
espressoCore = "3.5.1"
Expand All @@ -38,6 +40,7 @@ mockKVersion = "1.13.10"
playServicesAuth = "21.2.0"
rulesVersion = "1.5.0"
runnerVersion = "1.5.2"
securityCryptoKtx = "1.1.0-alpha06"
targetSdk = "34"
testCoreVersion = "1.5.0"
timberVersion = "5.0.1"
Expand All @@ -50,18 +53,20 @@ android-gradle = { group = "com.android.tools.build", name = "gradle", version.r
android-tools-common = { group = "com.android.tools", name = "common", version.ref = "androidTools" }
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
androidx-health-connect-client = { module = "androidx.health.connect:connect-client", version.ref = "healthConnectClient" }
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-core-testing = { group = "androidx.arch.core", name = "core-testing", version.ref = "coreTestingVersion" }
androidx-credentials-play-services-auth = { module = "androidx.credentials:credentials-play-services-auth", version.ref = "credentialsPlayServicesAuth" }
androidx-datastore-preferences = { module = "androidx.datastore:datastore-preferences", version.ref = "datastorePreferences" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
androidx-health-connect-client = { module = "androidx.health.connect:connect-client", version.ref = "healthConnectClient" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleKtx" }
androidx-lifecycle-view-model-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" }
androidx-security-crypto-ktx = { group = "androidx.security", name = "security-crypto-ktx", version.ref = "securityCryptoKtx" }
androidx-test-core = { group = "androidx.test", name = "core", version.ref = "testCoreVersion" }
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "runnerVersion" }
androidx-test-rules = { group = "androidx.test", name = "rules", version.ref = "rulesVersion" }
androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "runnerVersion" }
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-runtime = { group = "androidx.compose.runtime", name = "runtime" }
Expand All @@ -71,6 +76,7 @@ compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4" }
compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
core-ktx = { group = "androidx.test", name = "core-ktx", version.ref = "coreKtxVersion" }
coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutinesVersion" }
coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutinesVersion" }
coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutinesVersion" }
Expand Down
1 change: 1 addition & 0 deletions modules/storage/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
80 changes: 80 additions & 0 deletions modules/storage/README.MD
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Module storage

The storage module provides components for managing storage in your application. It includes classes
for handling key-value/file storage and secure storage of data.

## Usage

To use the Storage module in your project, add the following dependency to your `build.gradle` file:

```gradle
dependencies {
implementation(":modules:storage")`
}
```

and provide the wanted storage implementation with Hilt DI. There are the following storage
implementations:

- `EncryptedFileStorage` for the `FileStorage` interface
- `EncryptedSharedPreferencesStorage` for the `KeyValueStorage` interface
- `LocalStorage` for the `KeyValueStorage` interface

## Key-Value Storage

The key-value storage provides a simple interface for storing and retrieving key-value pairs:

```kotlin
interface KeyValueStorage {
suspend fun <T : Any> saveData(key: PreferenceKey<T>, data: T)
fun <T> readData(key: PreferenceKey<T>): Flow<T?>
suspend fun <T> readDataBlocking(key: PreferenceKey<T>): T?
suspend fun <T> deleteData(key: PreferenceKey<T>)
}
```

It can be used like this:

```kotlin
val stringKey = PreferenceKey.StringKey("user_name")
keyValueStorage.saveData(stringKey, "test_user_name")
keyValueStorage.readDataBlocking(stringKey)?.let {
println("Read string data blocking: $it")
}
keyValueStorage.deleteData(stringKey)
```

or you can use the `Flow` interface to observe changes:

```kotlin
val job = launch {
keyValueStorage.readData(stringKey).collect { data: String? ->
println("Read string data: $data")
}
}
```

## File Storage

The file storage provides a simple interface for storing and retrieving files:

```kotlin
interface FileStorage {
suspend fun readFile(fileName: String): ByteArray?
suspend fun deleteFile(fileName: String)
suspend fun saveFile(fileName: String, data: ByteArray)
}
```

It can be used like this:

```kotlin
val fileName = "testFile.data"
val data = "Hello, Stanford!".toByteArray()
fileStorage.saveFile(fileName, data)
val readData = fileStorage.readFile(fileName)
readData?.let {
println("Read file data: ${String(it)}")
}
fileStorage.deleteFile(fileName)
```
15 changes: 15 additions & 0 deletions modules/storage/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
plugins {
alias(libs.plugins.spezi.library)
alias(libs.plugins.spezi.hilt)
}

android {
namespace = "edu.stanford.spezi.modules.storage"
}

dependencies {
implementation(project(":core:coroutines"))
implementation(libs.androidx.datastore.preferences)
implementation(libs.androidx.security.crypto.ktx)
implementation(libs.core.ktx)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package edu.stanford.spezi.modules.storage.file

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.google.common.truth.Truth.assertThat
import edu.stanford.spezi.core.testing.runTestUnconfined
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class EncryptedFileKeyValueStorageTest {
private var context: Context = ApplicationProvider.getApplicationContext()
private var fileStorage: FileStorage =
EncryptedFileStorage(
context = context,
ioDispatcher = UnconfinedTestDispatcher(),
)
private val fileName = "testFile"

@After
fun tearDown() = runTestUnconfined {
// Delete the file after each test to clean up
fileStorage.deleteFile(fileName)
}

@Test
fun `it should save and read file correctly`() = runTestUnconfined {
// Given
val data = "Hello, World!".toByteArray()

// When
fileStorage.saveFile(fileName, data)

// Then
val readData = fileStorage.readFile(fileName)
assertThat(readData).isEqualTo(data)
}

@Test
fun `it should return null when reading non-existent file`() = runTestUnconfined {
// Given
val fileName = "nonExistentFile"

// When
val readData = fileStorage.readFile(fileName)

// Then
assertThat(readData).isNull()
}

@Test
fun `it should overwrite existing file when saving with same filename`() = runTestUnconfined {
// Given
val initialData = "Hello, World!".toByteArray()
val newData = "New data".toByteArray()

// When
fileStorage.saveFile(fileName, initialData)
fileStorage.saveFile(fileName, newData)

// Then
val readData = fileStorage.readFile(fileName)
assertThat(readData).isEqualTo(newData)
}

@Test
fun `it should delete file correctly`() = runTestUnconfined {
// Given
val data = "Hello, World!".toByteArray()
fileStorage.saveFile(fileName, data)

// When
fileStorage.deleteFile(fileName)

// Then
val readData = fileStorage.readFile(fileName)
assertThat(readData).isNull()
}
}
Loading

0 comments on commit 19b0573

Please sign in to comment.