diff --git a/app/build.gradle b/app/build.gradle index d96658155..d1a3ce433 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -55,7 +55,6 @@ android { } debug { enableUnitTestCoverage true - testCoverageEnabled true } } compileOptions { @@ -161,6 +160,9 @@ dependencies { // Firebase implementation(platform(libs.firebase.bom)) implementation(libs.firebase.analytics) + + // Security + implementation(libs.androidx.security) } tasks.withType(Test) { diff --git a/app/src/main/java/com/appunite/loudius/domain/store/UserLocalDataSourceImpl.kt b/app/src/main/java/com/appunite/loudius/domain/store/UserLocalDataSourceImpl.kt index e81bb002c..ad486748e 100644 --- a/app/src/main/java/com/appunite/loudius/domain/store/UserLocalDataSourceImpl.kt +++ b/app/src/main/java/com/appunite/loudius/domain/store/UserLocalDataSourceImpl.kt @@ -17,7 +17,8 @@ package com.appunite.loudius.domain.store import android.content.Context -import android.content.SharedPreferences +import androidx.security.crypto.EncryptedSharedPreferences +import androidx.security.crypto.MasterKey import com.appunite.loudius.network.model.AccessToken interface UserLocalDataSource { @@ -32,8 +33,19 @@ class UserLocalDataSourceImpl(context: Context) : UserLocalDataSource { private const val KEY_ACCESS_TOKEN = "access_token" } - private val sharedPreferences: SharedPreferences = - context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE) + private val masterKey = MasterKey.Builder(context) + .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) + .build() + + private val sharedPreferences by lazy { + EncryptedSharedPreferences.create( + context, + FILE_NAME, + masterKey, + EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, + EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM, + ) + } override fun saveAccessToken(accessToken: AccessToken) { sharedPreferences.edit().putString(KEY_ACCESS_TOKEN, accessToken).apply() diff --git a/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt b/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt index a3fde2549..cca0644b7 100644 --- a/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt +++ b/app/src/test/java/com/appunite/loudius/di/CheckModulesTest.kt @@ -17,13 +17,11 @@ package com.appunite.loudius.di import android.content.Context -import android.content.SharedPreferences import com.appunite.loudius.appModule import com.appunite.loudius.util.MainDispatcherExtension import com.google.firebase.analytics.FirebaseAnalytics import io.mockk.every import io.mockk.mockk -import io.mockk.mockkClass import io.mockk.mockkStatic import org.junit.jupiter.api.Test import org.junit.jupiter.api.extension.ExtendWith @@ -36,9 +34,7 @@ class CheckModulesTest : KoinTest { @Test fun verifyKoinApp() { - val mockContext = mockkClass(Context::class) - val mockSharedPref = mockkClass(SharedPreferences::class) - every { mockContext.getSharedPreferences(any(), any()) } returns mockSharedPref + val mockContext = mockk(relaxed = true) val mockFirebaseAnalytics = mockk() mockkStatic(FirebaseAnalytics::class) diff --git a/app/src/test/java/com/appunite/loudius/domain/UserLocalDataSourceTest.kt b/app/src/test/java/com/appunite/loudius/domain/UserLocalDataSourceTest.kt index bbcf303eb..954f715c0 100644 --- a/app/src/test/java/com/appunite/loudius/domain/UserLocalDataSourceTest.kt +++ b/app/src/test/java/com/appunite/loudius/domain/UserLocalDataSourceTest.kt @@ -16,9 +16,7 @@ package com.appunite.loudius.domain -import android.content.Context -import com.appunite.loudius.domain.store.UserLocalDataSourceImpl -import com.appunite.loudius.fakes.FakeSharedPreferences +import com.appunite.loudius.domain.store.UserLocalDataSource import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.Test @@ -27,11 +25,11 @@ import strikt.assertions.isEmpty import strikt.assertions.isEqualTo class UserLocalDataSourceTest { - private val sharedPreferences = FakeSharedPreferences() - private val context = mockk { - every { getSharedPreferences(any(), any()) } returns sharedPreferences + + private val userLocalDataSource = mockk { + every { saveAccessToken(any()) } returns Unit + every { getAccessToken() } returns "" } - private val userLocalDataSource = UserLocalDataSourceImpl(context) @Test fun `GIVEN the app is started first time WHEN getting access token THEN token is empty`() { @@ -42,6 +40,8 @@ class UserLocalDataSourceTest { @Test fun `WHEN token is set THEN token can be retrieved`() { + every { userLocalDataSource.getAccessToken() } returns "someToken" + userLocalDataSource.saveAccessToken("someToken") val result = userLocalDataSource.getAccessToken() diff --git a/app/src/test/java/com/appunite/loudius/fakes/FakeSharedPreferences.kt b/app/src/test/java/com/appunite/loudius/fakes/FakeSharedPreferences.kt deleted file mode 100644 index efdebfd69..000000000 --- a/app/src/test/java/com/appunite/loudius/fakes/FakeSharedPreferences.kt +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2023 AppUnite S.A. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.appunite.loudius.fakes - -import android.content.SharedPreferences - -class FakeSharedPreferences : SharedPreferences { - - private val map = mutableMapOf() - - private inner class Editor : SharedPreferences.Editor { - private val updates = mutableMapOf() - - override fun putString(key: String, value: String?): SharedPreferences.Editor { - updates[key] = value - return this - } - - override fun putStringSet( - key: String, - value: MutableSet? - ): SharedPreferences.Editor = - TODO("Not yet implemented") - - override fun putInt(key: String, value: Int): SharedPreferences.Editor = - TODO("Not yet implemented") - - override fun putLong(key: String, value: Long): SharedPreferences.Editor = - TODO("Not yet implemented") - - override fun putFloat(key: String, value: Float): SharedPreferences.Editor = - TODO("Not yet implemented") - - override fun putBoolean(key: String, value: Boolean): SharedPreferences.Editor = - TODO("Not yet implemented") - - override fun remove(key: String?): SharedPreferences.Editor = TODO("Not yet implemented") - - override fun clear(): SharedPreferences.Editor = TODO("Not yet implemented") - - override fun commit(): Boolean { - apply() - return true - } - - override fun apply() { - map.putAll(updates) - } - } - - override fun getAll(): MutableMap { - TODO("Not yet implemented") - } - - override fun getString(key: String?, defValue: String?): String? = - map[key] as String? ?: defValue - - override fun getStringSet(key: String?, defValues: MutableSet?): MutableSet? { - TODO("Not yet implemented") - } - - override fun getInt(key: String?, defValue: Int): Int { - TODO("Not yet implemented") - } - - override fun getLong(key: String?, defValue: Long): Long { - TODO("Not yet implemented") - } - - override fun getFloat(key: String?, defValue: Float): Float { - TODO("Not yet implemented") - } - - override fun getBoolean(key: String?, defValue: Boolean): Boolean { - TODO("Not yet implemented") - } - - override fun contains(key: String?): Boolean { - TODO("Not yet implemented") - } - - override fun edit(): SharedPreferences.Editor = Editor() - - override fun registerOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { - TODO("Not yet implemented") - } - - override fun unregisterOnSharedPreferenceChangeListener(listener: SharedPreferences.OnSharedPreferenceChangeListener?) { - TODO("Not yet implemented") - } -} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d730c6d25..03acaaae8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,6 +28,7 @@ mockwebserver = "4.11.0" mockkAndroid = "1.13.5" okhttp3 = "4.12.0" robolectric = "4.10.3" +securityCrypto = "1.1.0-alpha06" slf4jApi = "2.0.11" striktCore = "0.34.1" striktMockk = "0.34.1" @@ -71,6 +72,7 @@ androidx-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersi androidx-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitVersion" } androidx-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "ktx" } androidx-lifecycle = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleKtx" } +androidx-security = { group = "androidx.security", name = "security-crypto", version.ref = "securityCrypto" } androidx-test-orchestartor = { group = "androidx.test", name = "orchestrator", version.ref = "testOrchestrator" } androidx-test-runner = { group = "androidx.test", name = "runner", version.ref = "testRunner" } androidx-test-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "testUiAutomator" } diff --git a/module-jacoco.gradle b/module-jacoco.gradle index 7fda6af50..a6b209673 100644 --- a/module-jacoco.gradle +++ b/module-jacoco.gradle @@ -1,5 +1,9 @@ apply plugin: 'jacoco' +jacoco { + toolVersion '0.8.8' +} + tasks.register('codeCoverage', JacocoReport) { reports {