Commit 8b887fc9 authored by stopcovid@lunabee.com's avatar stopcovid@lunabee.com
Browse files

Update to 3.0.0

- Clea
parent 24f87ebd
......@@ -14,10 +14,12 @@ enum class EnvConstant {
Prod {
override val captchaApiKey: String = "6LettPsUAAAAAHYaFdRBOilHUgmTMSIPKNZN4D7l"
override val baseUrl: String = "https://api.tousanticovid.gouv.fr"
override val warningBaseUrl: String = "https://tacw.tousanticovid.gouv.fr"
override val analyticsBaseUrl: String = "https://analytics-api.tousanticovid.gouv.fr"
override val certificateSha256: String = "sha256/xrPKKhmYeHgk4v57GcqYPrFpnI3f1FTmEfol9WIicaI="
override val warningCertificateSha256: String = "sha256/b7w+uqyD+XILNIlRc3XVmEROwFCVTv5yOchb2i5FJbo="
override val cleaStatusBaseUrl: String = "https://s3.fr-par.scw.cloud/clea-batch/"
override val cleaStatusCertificateSha256: String = "sha256/LB5QyMD8qDE1y12jAq/yey1VpjsBbVfFNOblb7QyaR0="
override val cleaReportBaseUrl: String = "https://signal-api.tousanticovid.gouv.fr/"
override val cleaReportCertificateSha256: String = "sha256/tnjjbtuXZUSyK1uReQhvmtspPS7IUl95cs9G8RvfVUs="
override val analyticsBaseUrl: String = "https://analytics-api.tousanticovid.gouv.fr"
override val analyticsCertificateSha256: String = "sha256/KhFx3fIev58nbXs9m2WqXDbqYrE/7r4J9cP1QqPHtVk="
override val configFilename: String = "config.json"
override val calibrationFilename: String = "calibrationBle.json"
......@@ -26,10 +28,12 @@ enum class EnvConstant {
abstract val captchaApiKey: String
abstract val baseUrl: String
abstract val warningBaseUrl: String
abstract val analyticsBaseUrl: String
abstract val certificateSha256: String
abstract val warningCertificateSha256: String
abstract val cleaStatusBaseUrl: String
abstract val cleaStatusCertificateSha256: String
abstract val cleaReportBaseUrl: String
abstract val cleaReportCertificateSha256: String
abstract val analyticsBaseUrl: String
abstract val analyticsCertificateSha256: String
abstract val configFilename: String
abstract val calibrationFilename: String
......
......@@ -23,8 +23,10 @@ internal class ApiConfiguration(
val versionCalibrationBle: Int,
@SerializedName("app.apiVersion")
val apiVersion: String,
@SerializedName("app.warningApiVersion")
val warningApiVersion: String,
@SerializedName("app.clea.statusApiVersion")
val cleaStatusApiVersion: String,
@SerializedName("app.clea.reportApiVersion")
val cleaReportApiVersion: String,
@SerializedName("app.displayAttestation")
val displayAttestation: Boolean,
@SerializedName("app.displayVaccination")
......@@ -127,7 +129,6 @@ internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
version = version,
versionCalibrationBle = versionCalibrationBle,
apiVersion = apiVersion,
warningApiVersion = warningApiVersion,
displayAttestation = displayAttestation,
displayVaccination = displayVaccination,
dataRetentionPeriod = dataRetentionPeriod,
......@@ -185,7 +186,9 @@ internal fun ApiConfiguration.toDomain(gson: Gson) = Configuration(
object : TypeToken<List<WalletPublicKey>?>() {}.type
),
testCertificateValidityThresholdInHours = testCertificateValidityThresholdInHours,
cleaReportApiVersion = cleaReportApiVersion,
cleaStatusApiVersion = cleaStatusApiVersion,
displaySanitaryCertificatesValidation = displaySanitaryCertificatesValidation,
isAnalyticsOn = isAnalyticsOn,
analyticsApiVersion = analyticsApiVersion,
analyticsApiVersion = analyticsApiVersion
)
\ No newline at end of file
......@@ -10,5 +10,7 @@
package com.lunabeestudio.domain.extension
fun Long.unixTimeMsToNtpTimeS(): Long = this / 1000L + 2208988800
fun Long.ntpTimeSToUnixTimeMs(): Long = (this - 2208988800) * 1000L
\ No newline at end of file
fun Long.unixTimeMsToNtpTimeS(): Long = (this / 1000L).unixTimeSToNtpTimeS()
fun Long.unixTimeSToNtpTimeS(): Long = this + 2208988800
fun Long.ntpTimeSToUnixTimeMs(): Long = ntpTimeSToUnixTimeS() * 1000L
fun Long.ntpTimeSToUnixTimeS(): Long = (this - 2208988800)
package com.lunabeestudio.domain.model
class Cluster(
val ltid: String,
val exposures: List<ClusterExposure>?
)
\ No newline at end of file
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2021/19/04 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.domain.model
class ClusterExposure(
val startTimeNTP: Long,
val duration: Long,
val riskLevel: Float
)
\ No newline at end of file
package com.lunabeestudio.domain.model
class ClusterIndex(
val iteration: Int,
val clusterPrefixList: List<String>
)
......@@ -14,7 +14,8 @@ class Configuration(
var version: Int,
val versionCalibrationBle: Int,
val apiVersion: String,
val warningApiVersion: String,
val cleaReportApiVersion: String,
val cleaStatusApiVersion: String,
var displayAttestation: Boolean,
var displayVaccination: Boolean,
val dataRetentionPeriod: Int,
......
......@@ -2,25 +2,8 @@ package com.lunabeestudio.domain.model
data class VenueQrCode(
val id: String,
val uuid: String,
val qrType: VenueQrType,
val venueType: String,
val ltid: String,
val ntpTimestamp: Long,
val venueCategory: Int?,
val venueCapacity: Int?,
val payload: String
val base64URL: String,
val version: Int
)
enum class VenueQrType(val value: Int) {
STATIC(0), DYNAMIC(1);
companion object {
fun fromValue(value: Int): VenueQrType? {
return when (value) {
0 -> STATIC
1 -> DYNAMIC
else -> null
}
}
}
}
\ No newline at end of file
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2020/20/05 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.remote
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import com.lunabeestudio.framework.remote.datasource.CleaDataSource
import com.lunabeestudio.framework.testutils.ResourcesHelper
import com.lunabeestudio.robert.model.RobertResultData
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Test
class CleaServiceTest {
private lateinit var dataSource: CleaDataSource
private lateinit var server: MockWebServer
@Before
fun setUp() {
server = MockWebServer()
server.start()
dataSource = CleaDataSource(
ApplicationProvider.getApplicationContext(),
server.url("/").toString(),
"sha256/tb6+ch/VeDZl6rHlWfL4fCAQHyCmkexJhYBa7drUmxY=",
server.url("/").toString(),
"sha256/tb6+ch/VeDZl6rHlWfL4fCAQHyCmkexJhYBa7drUmxY="
)
}
@Test
fun getClusterIndexTestOK() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("clusterIndexSuccess"))
)
val result = runBlocking {
dataSource.cleaClusterIndex("v1")
}
assertThat(result).isInstanceOf(RobertResultData.Success::class.java)
}
@Test
fun getClusterIndexTestKO() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("clusterIndexFailure"))
)
val result = runBlocking {
dataSource.cleaClusterIndex("v1")
}
assertThat(result).isInstanceOf(RobertResultData.Failure::class.java)
}
@After
fun tearDown() {
server.shutdown()
}
}
\ No newline at end of file
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* Authors
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Created by Lunabee Studio / Date - 2020/20/05 - for the TOUS-ANTI-COVID project
*/
package com.lunabeestudio.framework.remote
import androidx.test.core.app.ApplicationProvider
import com.google.common.truth.Truth.assertThat
import com.lunabeestudio.domain.model.ServerStatusUpdate
import com.lunabeestudio.framework.remote.datasource.ServiceDataSource
import com.lunabeestudio.framework.testutils.ResourcesHelper
import com.lunabeestudio.robert.model.BackendException
import com.lunabeestudio.robert.model.ErrorCode
import com.lunabeestudio.robert.model.RobertResult
import com.lunabeestudio.robert.model.RobertResultData
import com.lunabeestudio.robert.model.UnauthorizedException
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Test
class RobertServiceTest {
private lateinit var server: MockWebServer
private lateinit var dataSource: ServiceDataSource
@Before
fun setUp() {
server = MockWebServer()
server.start()
dataSource = ServiceDataSource(
ApplicationProvider.getApplicationContext(),
server.url("/api/v1.0/").toString(),
""
)
}
@Test
fun captcha() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("captchaSuccess"))
)
val result = runBlocking {
dataSource.generateCaptcha("", "", "")
}
assertThat(result).isInstanceOf(RobertResultData.Success::class.java)
result as RobertResultData.Success
assertThat(result.data).isEqualTo("228482eb770547059425e58ca6652c8a")
testDataErrors {
dataSource.generateCaptcha("", "", "")
}
}
@Test
fun registerV2Test() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("registerV2Success"))
)
val result = runBlocking {
dataSource.registerV2("", "", "", "")
}
assertThat(result).isInstanceOf(RobertResultData.Success::class.java)
result as RobertResultData.Success
assertThat(result.data.message).isEqualTo("The application did register successfully")
assertThat(result.data.timeStart).isEqualTo(3799958400L)
assertThat(result.data.tuples).isEqualTo("test")
testDataErrors {
dataSource.registerV2("", "", "", "")
}
}
@Test
fun statusTest() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("statusSuccess"))
)
val result = runBlocking {
dataSource.status("", ServerStatusUpdate("", 0L, "", ""))
}
assertThat(result).isInstanceOf(RobertResultData.Success::class.java)
assertThat((result as RobertResultData.Success).data.riskLevel).isEqualTo(0)
assertThat(result.data.ntpLastContactS).isEqualTo(3814601612L)
assertThat(result.data.ntpLastRiskScoringS).isEqualTo(3814601613L)
assertThat(result.data.message).isEqualTo("message")
assertThat(result.data.tuples).isEqualTo("tuples")
assertThat(result.data.declarationToken).isEqualTo("declarationToken")
assertThat(result.data.analyticsToken).isEqualTo("analyticsToken")
testDataErrors {
dataSource.status("", ServerStatusUpdate("", 0L, "", ""))
}
}
@Test
fun reportTest() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("reportSuccess"))
)
var result = runBlocking {
dataSource.report("", "", emptyList())
}
assertThat(result).isInstanceOf(RobertResultData.Success::class.java)
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("reportWithTokenSuccess"))
)
result = runBlocking {
dataSource.report("", "", emptyList())
}
assertThat(result).isInstanceOf(RobertResultData.Success::class.java)
assertThat((result as RobertResultData.Success).data.reportValidationToken).isEqualTo("token")
testDataErrors {
dataSource.report("", "", emptyList())
}
}
@Test
fun unregisterTest() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("unregisterSuccess"))
)
val result = runBlocking {
dataSource.unregister("", ServerStatusUpdate("", 0L, "", ""))
}
assertThat(result).isInstanceOf(RobertResult.Success::class.java)
testErrors {
dataSource.unregister("", ServerStatusUpdate("", 0L, "", ""))
}
}
@Test
fun deleteExposureHistory() {
server.enqueue(
MockResponse().setResponseCode(200)
.setBody(ResourcesHelper.readTestFileAsString("deleteExposureHistorySuccess"))
)
val result = runBlocking {
dataSource.deleteExposureHistory("", ServerStatusUpdate("", 0L, "", ""))
}
assertThat(result).isInstanceOf(RobertResult.Success::class.java)
testErrors {
dataSource.deleteExposureHistory("", ServerStatusUpdate("", 0L, "", ""))
}
}
private fun testDataErrors(wsCall: suspend () -> Any) {
server.enqueue(MockResponse().setResponseCode(401))
val unauthorizedResult = runBlocking {
wsCall()
}
assertThat(unauthorizedResult).isInstanceOf(RobertResultData.Failure::class.java)
assertThat((unauthorizedResult as RobertResultData.Failure<*>).error).isInstanceOf(UnauthorizedException::class.java)
assertThat(unauthorizedResult.error?.errorCode).isEqualTo(ErrorCode.UNAUTHORIZED)
server.enqueue(MockResponse().setResponseCode(500))
val backendResult = runBlocking {
wsCall()
}
assertThat(backendResult).isInstanceOf(RobertResultData.Failure::class.java)
assertThat((backendResult as RobertResultData.Failure<*>).error).isInstanceOf(BackendException::class.java)
assertThat(backendResult.error?.errorCode).isEqualTo(ErrorCode.BACKEND)
assertThat(backendResult.error?.message).isEqualTo(BackendException().message)
}
private fun testErrors(wsCall: suspend () -> Any) {
server.enqueue(MockResponse().setResponseCode(401))
val unauthorizedResult = runBlocking {
wsCall()
}
assertThat(unauthorizedResult).isInstanceOf(RobertResult.Failure::class.java)
assertThat((unauthorizedResult as RobertResult.Failure).error).isInstanceOf(UnauthorizedException::class.java)
assertThat(unauthorizedResult.error?.errorCode).isEqualTo(ErrorCode.UNAUTHORIZED)
server.enqueue(MockResponse().setResponseCode(500))
val backendResult = runBlocking {
wsCall()
}
assertThat(backendResult).isInstanceOf(RobertResult.Failure::class.java)
assertThat((backendResult as RobertResult.Failure).error).isInstanceOf(BackendException::class.java)
assertThat(backendResult.error?.errorCode).isEqualTo(ErrorCode.BACKEND)
assertThat(backendResult.error?.message).isEqualTo(BackendException().message)
}
@After
fun tearDown() {
server.shutdown()
}
}
{
"i" : 42,
"c" :
[
"85e909",
"b7f6",
"b6ddadfb"
]
}
{
"i" : 42,
"c" :
[
"85e909",
"b7f6",
"b6ddadfb",,
]
}
{
"i" : 42,
"c" :
[
"85e909",
"b7f6",
"b6ddadfb"
]
}
......@@ -5,4 +5,18 @@ fun String.removePublicKeyDecoration(): String {
.replace("-----BEGIN PUBLIC KEY-----", "")
.replace("-----END PUBLIC KEY-----", "")
}
\ No newline at end of file
}
private val replaceCharacters = arrayOf(
arrayOf("+", "-"),
arrayOf("/", "_")
)
fun String.fromBase64URL(): String {
var result = this
replaceCharacters.forEach {
result = result.replace(it[1], it[0])
}
return result
}
......@@ -90,6 +90,10 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
get() = getEncryptedValue(SHARED_PREF_KEY_CURRENT_WARNING_AT_RISK_STATUS, AtRiskStatus::class.java)
set(value) = setEncryptedValue(SHARED_PREF_KEY_CURRENT_WARNING_AT_RISK_STATUS, value)
override var cleaLastStatusIteration: Int?
get() = getEncryptedValue(SHARED_PREF_KEY_CLEA_LAST_STATUS_ITERATION, Int::class.java)
set(value) = setEncryptedValue(SHARED_PREF_KEY_CLEA_LAST_STATUS_ITERATION, value)
override var atRiskModelVersion: Int?
get() = getEncryptedValue(SHARED_PREF_KEY_AT_RISK_MODEL_VERSION, Int::class.java)
set(value) = setEncryptedValue(SHARED_PREF_KEY_AT_RISK_MODEL_VERSION, value)
......@@ -359,6 +363,9 @@ class SecureKeystoreDataSource(context: Context, private val cryptoManager: Loca
private const val SHARED_PREF_KEY_DECLARATION_TOKEN = "shared.pref.declaration_token"
private const val SHARED_PREF_KEY_WALLET_CERTIFICATES = "shared.pref.wallet_certificates"
// Clea
private const val SHARED_PREF_KEY_CLEA_LAST_STATUS_ITERATION = "shared.pref.clea_last_status_iteration"
// Add on to ROBERT for isolation
private const val SHARED_PREF_KEY_REPORT_SYMPTOMS_DATE = "shared.pref.reportSymptomsDate"
private const val SHARED_PREF_KEY_REPORT_POSITIVE_TEST_DATE = "shared.pref.reportPositiveTestDate"
......
......@@ -39,14 +39,6 @@ object RetrofitClient {
.build().create(clazz)
}
internal fun <T> getWarningService(context: Context, warningBaseUrl: String, warningCertificateSHA256: String, clazz: Class<T>): T {
return Retrofit.Builder()
.baseUrl(warningBaseUrl.toHttpUrl())
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().create()))
.client(getDefaultOKHttpClient(context, warningBaseUrl, warningCertificateSHA256))
.build().create(clazz)
}
internal fun <T> getFileService(context: Context, baseUrl: String, certificateSHA256: String, clazz: Class<T>): T {
return Retrofit.Builder()
.baseUrl(baseUrl.toHttpUrl())
......@@ -72,6 +64,8 @@ object RetrofitClient {
.addTrustedCertificate(certificateFromString(context, "api_tousanticovid_gouv_fr"))
.addTrustedCertificate(certificateFromString(context, "tacw_tousanticovid_gouv_fr"))
.addTrustedCertificate(certificateFromString(context, "app_tousanticovid_gouv_fr"))
.addTrustedCertificate(certificateFromString(context, "s3_fr_par_scw_cloud"))
.addTrustedCertificate(certificateFromString(context, "signal_api_tousanticovid_gouv_fr"))
.build()
sslSocketFactory(certificates.sslSocketFactory(), certificates.trustManager)
}
......
package com.lunabeestudio.framework.remote.datasource
import android.content.Context
import com.lunabeestudio.domain.model.Cluster
import com.lunabeestudio.domain.model.ClusterIndex
import com.lunabeestudio.domain.model.VenueQrCode
import com.lunabeestudio.framework.remote.RetrofitClient
import com.lunabeestudio.framework.remote.model.ApiWReportClea
import com.lunabeestudio.framework.remote.model.toDomain
import com.lunabeestudio.framework.remote.server.CleaReportApi
import com.lunabeestudio.framework.remote.server.CleaStatusApi
import com.lunabeestudio.framework.utils.RequestHelper
import com.lunabeestudio.robert.datasource.RemoteCleaDataSource
import com.lunabeestudio.robert.model.RobertResult
import com.lunabeestudio.robert.model.RobertResultData
class CleaDataSource(
val context: Context,
cleaReportBaseUrl: String,
cleaReportCertificateSha256: String,
cleaStatusBaseUrl: String,
cleaStatusCertificateSha256: String,
) : RemoteCleaDataSource {
private var filesDir = context.filesDir
private var cleaStatusApi: CleaStatusApi = RetrofitClient.getService(
context,
cleaStatusBaseUrl,
cleaStatusCertificateSha256,
CleaStatusApi::class.java
)
private var cleaReportApi: CleaReportApi = RetrofitClient.getService(
context,
cleaReportBaseUrl,