-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature/issue 42 local storage encryption (#47)
# *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
Showing
15 changed files
with
673 additions
and
2 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
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 @@ | ||
/build |
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,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) | ||
``` |
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,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) | ||
} |
82 changes: 82 additions & 0 deletions
82
...idTest/kotlin/edu/stanford/spezi/modules/storage/file/EncryptedFileKeyValueStorageTest.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,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() | ||
} | ||
} |
Oops, something went wrong.