mirror of
https://github.com/ProtonMail/protoncore_android.git
synced 2026-06-14 09:54:49 +00:00
Add validation to the version sent to the API in x-pm-appversion header
This commit is contained in:
+28
-5
@@ -6,7 +6,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## Fixed
|
||||
### Changes
|
||||
- Added validation for Client Version header (x-pm-appversion).
|
||||
|
||||
### New Injection
|
||||
- ApiManagerFactory now needs an extra `ClientVersionValidator` dependency, which is already included in the Network dagger module.
|
||||
|
||||
```kotlin
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApiFactory(
|
||||
...,
|
||||
clientVersionValidator: ClientVersionValidator,
|
||||
apiConnectionListener: ApiConnectionListener? = null,
|
||||
): ApiManagerFactory {
|
||||
return ApiManagerFactory(
|
||||
...,
|
||||
clientVersionValidator = clientVersionValidator,
|
||||
apiConnectionListener = apiConnectionListener,
|
||||
)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
### Fixed
|
||||
|
||||
- Links, outlined buttons and borderless buttons use text-accent color.
|
||||
|
||||
@@ -21,7 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [7.1.5]
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Removed Label dependency from Contact modules.
|
||||
- Fix invalid strings resources for Plans.
|
||||
@@ -29,13 +52,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [7.1.4]
|
||||
|
||||
## New
|
||||
### New
|
||||
|
||||
- `ProtonSettings` basic composables to build settings screens.
|
||||
- Enable clients to pass additional filtering function for the plans, according to their needs.
|
||||
- Added Room LabelConverters. Please add `LabelConverters` to your `TypeConverters`.
|
||||
|
||||
## Fixed
|
||||
### Fixed
|
||||
|
||||
- Stop decoding the base64 when decrypting the HashKey (to keep compatibility with drive web & iOS).
|
||||
- Encode the random bytes in base64 when generating the hash key.
|
||||
@@ -44,7 +67,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [7.1.3]
|
||||
|
||||
## New
|
||||
### New
|
||||
- Added functions to encrypt with compression to the crypto module and to the KeyHolder's context:
|
||||
`encryptAndSignTextWithCompression` and `encryptAndSignDataWithCompression`
|
||||
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ class CoreExampleApiClient @Inject constructor() : ApiClient {
|
||||
* Client's value for 'x-pm-appversion' header.
|
||||
*/
|
||||
override val appVersionHeader: String
|
||||
get() = "Android_1.14.0"
|
||||
get() = "android-mail@$VERSION_NAME"
|
||||
|
||||
/**
|
||||
* Client's value for 'User-Agent' header.
|
||||
|
||||
@@ -45,6 +45,7 @@ import me.proton.core.network.domain.ApiClient
|
||||
import me.proton.core.network.domain.NetworkManager
|
||||
import me.proton.core.network.domain.NetworkPrefs
|
||||
import me.proton.core.network.domain.client.ClientIdProvider
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
import me.proton.core.network.domain.client.ExtraHeaderProvider
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationListener
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationProvider
|
||||
@@ -112,7 +113,8 @@ class NetworkModule {
|
||||
humanVerificationListener: HumanVerificationListener,
|
||||
missingScopeListener: MissingScopeListener,
|
||||
extraHeaderProvider: ExtraHeaderProvider,
|
||||
apiConnectionListener: ApiConnectionListener? = null
|
||||
clientVersionValidator: ClientVersionValidator,
|
||||
apiConnectionListener: ApiConnectionListener? = null,
|
||||
): ApiManagerFactory {
|
||||
val certificatePins = if (BuildConfig.USE_DEFAULT_PINS) {
|
||||
NetWorkDataConstants.DEFAULT_SPKI_PINS
|
||||
@@ -147,7 +149,8 @@ class NetworkModule {
|
||||
)
|
||||
},
|
||||
extraHeaderProvider = extraHeaderProvider,
|
||||
apiConnectionListener = apiConnectionListener
|
||||
apiConnectionListener = apiConnectionListener,
|
||||
clientVersionValidator = clientVersionValidator,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,14 @@ public final class me/proton/core/network/dagger/BuildConfig {
|
||||
public fun <init> ()V
|
||||
}
|
||||
|
||||
public final class me/proton/core/network/dagger/NetworkModule_ProvideClientVersionValidatorFactory : dagger/internal/Factory {
|
||||
public fun <init> (Lme/proton/core/network/dagger/NetworkModule;)V
|
||||
public static fun create (Lme/proton/core/network/dagger/NetworkModule;)Lme/proton/core/network/dagger/NetworkModule_ProvideClientVersionValidatorFactory;
|
||||
public synthetic fun get ()Ljava/lang/Object;
|
||||
public fun get ()Lme/proton/core/network/domain/client/ClientVersionValidator;
|
||||
public static fun provideClientVersionValidator (Lme/proton/core/network/dagger/NetworkModule;)Lme/proton/core/network/domain/client/ClientVersionValidator;
|
||||
}
|
||||
|
||||
public final class me/proton/core/network/dagger/NetworkModule_ProvideCookieJarFactory : dagger/internal/Factory {
|
||||
public fun <init> (Lme/proton/core/network/dagger/NetworkModule;Ljavax/inject/Provider;)V
|
||||
public static fun create (Lme/proton/core/network/dagger/NetworkModule;Ljavax/inject/Provider;)Lme/proton/core/network/dagger/NetworkModule_ProvideCookieJarFactory;
|
||||
|
||||
@@ -24,6 +24,9 @@ plugins {
|
||||
publishOption.shouldBePublishedAsLib = true
|
||||
|
||||
dependencies {
|
||||
implementation(
|
||||
project(Module.networkDomain)
|
||||
)
|
||||
api(
|
||||
project(Module.networkData)
|
||||
)
|
||||
|
||||
@@ -24,9 +24,11 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.core.network.data.ProtonCookieStore
|
||||
import me.proton.core.network.data.client.ClientVersionValidatorImpl
|
||||
import me.proton.core.network.data.cookie.DiskCookieStorage
|
||||
import me.proton.core.network.data.cookie.MemoryCookieStorage
|
||||
import me.proton.core.network.data.ProtonCookieStore
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@@ -38,4 +40,7 @@ internal class NetworkModule {
|
||||
persistentStorage = DiskCookieStorage(context, ProtonCookieStore.DISK_COOKIE_STORAGE_NAME),
|
||||
sessionStorage = MemoryCookieStorage()
|
||||
)
|
||||
|
||||
@Provides
|
||||
fun provideClientVersionValidator(): ClientVersionValidator = ClientVersionValidatorImpl()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
public final class me/proton/core/network/data/ApiManagerFactory {
|
||||
public fun <init> (Ljava/lang/String;Lme/proton/core/network/domain/ApiClient;Lme/proton/core/network/domain/client/ClientIdProvider;Lme/proton/core/network/domain/server/ServerTimeListener;Lme/proton/core/network/domain/NetworkManager;Lme/proton/core/network/domain/NetworkPrefs;Lme/proton/core/network/domain/session/SessionProvider;Lme/proton/core/network/domain/session/SessionListener;Lme/proton/core/network/domain/humanverification/HumanVerificationProvider;Lme/proton/core/network/domain/humanverification/HumanVerificationListener;Lme/proton/core/network/domain/scopes/MissingScopeListener;Lme/proton/core/network/data/ProtonCookieStore;Lkotlinx/coroutines/CoroutineScope;[Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function0;Lme/proton/core/network/domain/client/ExtraHeaderProvider;Lme/proton/core/network/domain/serverconnection/ApiConnectionListener;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Lme/proton/core/network/domain/ApiClient;Lme/proton/core/network/domain/client/ClientIdProvider;Lme/proton/core/network/domain/server/ServerTimeListener;Lme/proton/core/network/domain/NetworkManager;Lme/proton/core/network/domain/NetworkPrefs;Lme/proton/core/network/domain/session/SessionProvider;Lme/proton/core/network/domain/session/SessionListener;Lme/proton/core/network/domain/humanverification/HumanVerificationProvider;Lme/proton/core/network/domain/humanverification/HumanVerificationListener;Lme/proton/core/network/domain/scopes/MissingScopeListener;Lme/proton/core/network/data/ProtonCookieStore;Lkotlinx/coroutines/CoroutineScope;[Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function0;Lme/proton/core/network/domain/client/ExtraHeaderProvider;Lme/proton/core/network/domain/serverconnection/ApiConnectionListener;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public fun <init> (Ljava/lang/String;Lme/proton/core/network/domain/ApiClient;Lme/proton/core/network/domain/client/ClientIdProvider;Lme/proton/core/network/domain/server/ServerTimeListener;Lme/proton/core/network/domain/NetworkManager;Lme/proton/core/network/domain/NetworkPrefs;Lme/proton/core/network/domain/session/SessionProvider;Lme/proton/core/network/domain/session/SessionListener;Lme/proton/core/network/domain/humanverification/HumanVerificationProvider;Lme/proton/core/network/domain/humanverification/HumanVerificationListener;Lme/proton/core/network/domain/scopes/MissingScopeListener;Lme/proton/core/network/data/ProtonCookieStore;Lkotlinx/coroutines/CoroutineScope;[Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function0;Lme/proton/core/network/domain/client/ExtraHeaderProvider;Lme/proton/core/network/domain/serverconnection/ApiConnectionListener;Lme/proton/core/network/domain/client/ClientVersionValidator;)V
|
||||
public synthetic fun <init> (Ljava/lang/String;Lme/proton/core/network/domain/ApiClient;Lme/proton/core/network/domain/client/ClientIdProvider;Lme/proton/core/network/domain/server/ServerTimeListener;Lme/proton/core/network/domain/NetworkManager;Lme/proton/core/network/domain/NetworkPrefs;Lme/proton/core/network/domain/session/SessionProvider;Lme/proton/core/network/domain/session/SessionListener;Lme/proton/core/network/domain/humanverification/HumanVerificationProvider;Lme/proton/core/network/domain/humanverification/HumanVerificationListener;Lme/proton/core/network/domain/scopes/MissingScopeListener;Lme/proton/core/network/data/ProtonCookieStore;Lkotlinx/coroutines/CoroutineScope;[Ljava/lang/String;Ljava/util/List;Lkotlin/jvm/functions/Function0;Lme/proton/core/network/domain/client/ExtraHeaderProvider;Lme/proton/core/network/domain/serverconnection/ApiConnectionListener;Lme/proton/core/network/domain/client/ClientVersionValidator;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
|
||||
public final fun create (Lme/proton/core/network/domain/session/SessionId;Lkotlin/reflect/KClass;Ljava/util/List;[Ljava/lang/String;Ljava/util/List;)Lme/proton/core/network/domain/ApiManager;
|
||||
public static synthetic fun create$default (Lme/proton/core/network/data/ApiManagerFactory;Lme/proton/core/network/domain/session/SessionId;Lkotlin/reflect/KClass;Ljava/util/List;[Ljava/lang/String;Ljava/util/List;ILjava/lang/Object;)Lme/proton/core/network/domain/ApiManager;
|
||||
public final fun getBaseOkHttpClient ()Lokhttp3/OkHttpClient;
|
||||
@@ -81,6 +81,11 @@ public final class me/proton/core/network/data/client/ClientIdProviderImpl : me/
|
||||
public fun getClientId (Lme/proton/core/network/domain/session/SessionId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
}
|
||||
|
||||
public final class me/proton/core/network/data/client/ClientVersionValidatorImpl : me/proton/core/network/domain/client/ClientVersionValidator {
|
||||
public fun <init> ()V
|
||||
public fun validate (Ljava/lang/String;)Z
|
||||
}
|
||||
|
||||
public final class me/proton/core/network/data/client/ExtraHeaderProviderImpl : me/proton/core/network/domain/client/ExtraHeaderProvider {
|
||||
public fun <init> ()V
|
||||
public fun addHeaders ([Lkotlin/Pair;)V
|
||||
|
||||
@@ -36,6 +36,7 @@ import me.proton.core.network.domain.DohProvider
|
||||
import me.proton.core.network.domain.NetworkManager
|
||||
import me.proton.core.network.domain.NetworkPrefs
|
||||
import me.proton.core.network.domain.client.ClientIdProvider
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
import me.proton.core.network.domain.client.ExtraHeaderProvider
|
||||
import me.proton.core.network.domain.handlers.ApiConnectionHandler
|
||||
import me.proton.core.network.domain.handlers.HumanVerificationInvalidHandler
|
||||
@@ -84,7 +85,8 @@ class ApiManagerFactory(
|
||||
private val alternativeApiPins: List<String> = Constants.ALTERNATIVE_API_SPKI_PINS,
|
||||
private val cache: () -> Cache? = { null },
|
||||
private val extraHeaderProvider: ExtraHeaderProvider? = null,
|
||||
private val apiConnectionListener: ApiConnectionListener?
|
||||
private val apiConnectionListener: ApiConnectionListener?,
|
||||
private val clientVersionValidator: ClientVersionValidator,
|
||||
) {
|
||||
|
||||
@OptIn(ObsoleteCoroutinesApi::class)
|
||||
@@ -103,6 +105,9 @@ class ApiManagerFactory(
|
||||
require(apiClient.timeoutSeconds >= ApiClient.MIN_TIMEOUT_SECONDS) {
|
||||
"Minimum timeout for ApiClient is ${ApiClient.MIN_TIMEOUT_SECONDS} seconds."
|
||||
}
|
||||
require(clientVersionValidator.validate(apiClient.appVersionHeader)) {
|
||||
"Invalid app version code: ${apiClient.appVersionHeader}."
|
||||
}
|
||||
OkHttpClient.Builder()
|
||||
.cache(cache())
|
||||
.connectTimeout(apiClient.timeoutSeconds, TimeUnit.SECONDS)
|
||||
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.network.data.client
|
||||
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
|
||||
class ClientVersionValidatorImpl : ClientVersionValidator {
|
||||
|
||||
override fun validate(versionName: String?): Boolean {
|
||||
val components = versionName?.split("@").orEmpty()
|
||||
if (components.count() != 2) return false
|
||||
val (name, version) = components
|
||||
return isValidName(name) && isValidVersion(version)
|
||||
}
|
||||
|
||||
private fun isValidName(name: String) =
|
||||
Regex("^[a-z_]+-[a-z_]+(?:-[a-z_]+)?\$").matches(name)
|
||||
|
||||
private fun isValidVersion(version: String) =
|
||||
Regex("^\\d+?\\.\\d+?\\.\\d+?(-((stable|RC|beta|alpha)(\\.\\d+)?|dev)|)?(\\+[0-9A-Za-z\\-]+)?\$")
|
||||
.matches(version)
|
||||
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import me.proton.core.network.domain.NetworkStatus
|
||||
import me.proton.core.network.domain.ResponseCodes
|
||||
import me.proton.core.network.domain.client.ClientId
|
||||
import me.proton.core.network.domain.client.ClientIdProvider
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
import me.proton.core.network.domain.handlers.RefreshTokenHandler
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationListener
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationProvider
|
||||
@@ -105,6 +106,9 @@ internal class ApiManagerTests {
|
||||
@MockK
|
||||
private lateinit var dohService: DohService
|
||||
|
||||
@MockK
|
||||
private lateinit var clientVersionValidator: ClientVersionValidator
|
||||
|
||||
private var time = 0L
|
||||
private var wallTime = 0L
|
||||
|
||||
@@ -144,7 +148,8 @@ internal class ApiManagerTests {
|
||||
mockk(),
|
||||
scope,
|
||||
cache = { null },
|
||||
apiConnectionListener = null
|
||||
apiConnectionListener = null,
|
||||
clientVersionValidator = clientVersionValidator,
|
||||
)
|
||||
|
||||
coEvery { dohService.getAlternativeBaseUrls(any()) } returns listOf(proxy1url)
|
||||
|
||||
@@ -24,6 +24,7 @@ import io.mockk.impl.annotations.MockK
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import me.proton.core.network.data.doh.DnsOverHttpsProviderRFC8484
|
||||
import me.proton.core.network.data.util.MockApiClient
|
||||
import me.proton.core.network.data.util.takeRequestWithDefaultTimeout
|
||||
import me.proton.core.network.domain.NetworkManager
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
@@ -93,6 +94,7 @@ internal class DohProviderTests {
|
||||
|
||||
val result = dohProvider.getAlternativeBaseUrls("https://$domain/")!!
|
||||
assertEquals(listOf("https://proxy.com/"), result)
|
||||
assertEquals("application/dns-message", webServer.takeRequest().headers["Accept"])
|
||||
val acceptHeader = webServer.takeRequestWithDefaultTimeout()?.headers?.get("Accept")
|
||||
assertEquals("application/dns-message", acceptHeader)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,12 +37,14 @@ import me.proton.core.network.data.util.MockSessionListener
|
||||
import me.proton.core.network.data.util.TestRetrofitApi
|
||||
import me.proton.core.network.data.util.TestTLSHelper
|
||||
import me.proton.core.network.data.util.prepareResponse
|
||||
import me.proton.core.network.data.util.takeRequestWithDefaultTimeout
|
||||
import me.proton.core.network.domain.ApiManager
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.network.domain.NetworkManager
|
||||
import me.proton.core.network.domain.NetworkPrefs
|
||||
import me.proton.core.network.domain.client.ClientId
|
||||
import me.proton.core.network.domain.client.ClientIdProvider
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
import me.proton.core.network.domain.client.CookieSessionId
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationDetails
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationListener
|
||||
@@ -126,6 +128,9 @@ internal class HumanVerificationTests {
|
||||
private val humanVerificationProvider = mockk<HumanVerificationProvider>()
|
||||
private val humanVerificationListener = mockk<HumanVerificationListener>()
|
||||
private val missingScopeListener = mockk<MissingScopeListener>(relaxed = true)
|
||||
private val clientVersionValidator = mockk<ClientVersionValidator> {
|
||||
every { validate(any()) } returns true
|
||||
}
|
||||
|
||||
private var sessionListener: SessionListener = MockSessionListener(
|
||||
onTokenRefreshed = { session -> this.session = session }
|
||||
@@ -169,7 +174,8 @@ internal class HumanVerificationTests {
|
||||
cookieJar,
|
||||
scope,
|
||||
cache = { null },
|
||||
apiConnectionListener = null
|
||||
apiConnectionListener = null,
|
||||
clientVersionValidator = clientVersionValidator,
|
||||
)
|
||||
every { networkManager.isConnectedToNetwork() } returns isNetworkAvailable
|
||||
|
||||
@@ -200,7 +206,7 @@ internal class HumanVerificationTests {
|
||||
pinningInit,
|
||||
::javaWallClockMs,
|
||||
prefs,
|
||||
cookieJar
|
||||
cookieJar,
|
||||
)
|
||||
|
||||
@After
|
||||
@@ -321,15 +327,15 @@ internal class HumanVerificationTests {
|
||||
coEvery { humanVerificationProvider.getHumanVerificationDetails(clientId) } returns humanVerificationDetails
|
||||
|
||||
backend(ApiManager.Call(0) { test() })
|
||||
val headers = webServer.takeRequest().headers
|
||||
val headers = webServer.takeRequestWithDefaultTimeout()?.headers
|
||||
verify(exactly = 1) {
|
||||
humanVerificationDetails.tokenCode
|
||||
}
|
||||
verify(exactly = 1) {
|
||||
humanVerificationDetails.tokenType
|
||||
}
|
||||
assertTrue(headers.contains(Pair("x-pm-human-verification-token-type", "captcha")))
|
||||
assertTrue(headers.contains(Pair("x-pm-human-verification-token", "captcha token")))
|
||||
assertTrue(headers?.contains(Pair("x-pm-human-verification-token-type", "captcha")) ?: false)
|
||||
assertTrue(headers?.contains(Pair("x-pm-human-verification-token", "captcha token")) ?: false)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -360,15 +366,15 @@ internal class HumanVerificationTests {
|
||||
coEvery { humanVerificationProvider.getHumanVerificationDetails(clientId) } returns humanVerificationDetails
|
||||
|
||||
backend(ApiManager.Call(0) { test() })
|
||||
val headers = webServer.takeRequest().headers
|
||||
val headers = webServer.takeRequestWithDefaultTimeout()?.headers
|
||||
verify(exactly = 1) {
|
||||
humanVerificationDetails.tokenCode
|
||||
}
|
||||
verify(exactly = 1) {
|
||||
humanVerificationDetails.tokenType
|
||||
}
|
||||
assertTrue(headers.contains(Pair("x-pm-human-verification-token-type", "captcha")))
|
||||
assertTrue(headers.contains(Pair("x-pm-human-verification-token", "captcha token")))
|
||||
assertTrue(headers?.contains(Pair("x-pm-human-verification-token-type", "captcha")) ?: false)
|
||||
assertTrue(headers?.contains(Pair("x-pm-human-verification-token", "captcha token")) ?: false)
|
||||
}
|
||||
|
||||
private fun javaWallClockMs(): Long = System.currentTimeMillis()
|
||||
|
||||
@@ -35,12 +35,14 @@ import me.proton.core.network.data.util.MockSessionListener
|
||||
import me.proton.core.network.data.util.TestRetrofitApi
|
||||
import me.proton.core.network.data.util.TestTLSHelper
|
||||
import me.proton.core.network.data.util.prepareResponse
|
||||
import me.proton.core.network.data.util.takeRequestWithDefaultTimeout
|
||||
import me.proton.core.network.domain.ApiManager
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.network.domain.NetworkManager
|
||||
import me.proton.core.network.domain.NetworkPrefs
|
||||
import me.proton.core.network.domain.client.ClientId
|
||||
import me.proton.core.network.domain.client.ClientIdProvider
|
||||
import me.proton.core.network.domain.client.ClientVersionValidator
|
||||
import me.proton.core.network.domain.client.ExtraHeaderProvider
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationDetails
|
||||
import me.proton.core.network.domain.humanverification.HumanVerificationListener
|
||||
@@ -91,6 +93,9 @@ internal class ProtonApiBackendTests {
|
||||
private val humanVerificationProvider = mockk<HumanVerificationProvider>()
|
||||
private val humanVerificationListener = mockk<HumanVerificationListener>()
|
||||
private val missingScopeListener = mockk<MissingScopeListener>(relaxed = true)
|
||||
private val clientVersionValidator = mockk<ClientVersionValidator> {
|
||||
every { validate(any()) } returns true
|
||||
}
|
||||
|
||||
private var sessionListener: SessionListener = MockSessionListener(
|
||||
onTokenRefreshed = { session -> this.session = session }
|
||||
@@ -136,7 +141,8 @@ internal class ProtonApiBackendTests {
|
||||
cookieJar,
|
||||
scope,
|
||||
cache = { null },
|
||||
apiConnectionListener = null
|
||||
apiConnectionListener = null,
|
||||
clientVersionValidator = clientVersionValidator,
|
||||
)
|
||||
|
||||
every { networkManager.isConnectedToNetwork() } returns isNetworkAvailable
|
||||
@@ -249,7 +255,7 @@ internal class ProtonApiBackendTests {
|
||||
webServer.prepareResponse(HttpURLConnection.HTTP_OK, "plain")
|
||||
|
||||
val result = backend(ApiManager.Call(0) { testPlain() })
|
||||
assertEquals("text/plain", webServer.takeRequest().headers["Accept"])
|
||||
assertEquals("text/plain", webServer.takeRequestWithDefaultTimeout()?.headers?.get("Accept"))
|
||||
assertTrue(result is ApiResult.Success)
|
||||
|
||||
assertEquals("plain", result.value)
|
||||
@@ -384,8 +390,8 @@ internal class ProtonApiBackendTests {
|
||||
|
||||
backend(ApiManager.Call(0) { test() })
|
||||
|
||||
val request = webServer.takeRequest()
|
||||
val headerFound = request.headers.any { it == extraHeader }
|
||||
val request = webServer.takeRequestWithDefaultTimeout()
|
||||
val headerFound = request?.headers?.any { it == extraHeader } ?: false
|
||||
Assert.assertTrue(headerFound)
|
||||
}
|
||||
}
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.network.data.client
|
||||
|
||||
import org.junit.Test
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class ClientVersionValidatorTests {
|
||||
|
||||
private val validator = ClientVersionValidatorImpl()
|
||||
|
||||
@Test
|
||||
fun `Null version will fail validation`() {
|
||||
val version: String? = null
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Empty version will fail validation`() {
|
||||
val version = ""
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Only version number info will fail validation`() {
|
||||
val version = "1.2.3"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with invalid characters in will fail validation`() {
|
||||
val version = "ANDROID-Mail@1.2.3" // Should be lowercase
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with invalid separators will fail validation`() {
|
||||
val version = "android_mail_1.2.3"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with valid section info will be validated`() {
|
||||
val version = "android-mail-somesection_info@1.2.3"
|
||||
assertTrue { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with no section info will be validated`() {
|
||||
val version = "android-mail@1.2.3"
|
||||
assertTrue { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with invalid characters in section info will fail validation`() {
|
||||
val version = "android-mail-somesection.info@1.2.3"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with empty section info but separator will fail validation`() {
|
||||
val version = "android-mail-@1.2.3"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with empty version metadata info but separator will fail validation`() {
|
||||
val version = "android-mail@1.2.3+"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with empty version identifier info but separator will fail validation`() {
|
||||
val version = "android-mail@1.2.3-"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with invalid characters in version metadata will fail validation`() {
|
||||
val version = "android-mail@1.2.3+some.metadata"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with versioned dev identifier version will fail validation`() {
|
||||
val version = "android-mail@1.2.3-dev.1"
|
||||
assertFalse { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with only dev identifier version will pass validation`() {
|
||||
val version = "android-mail@1.2.3-dev"
|
||||
assertTrue { validator.validate(version) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with versioned stable, RC, beta or alpha identifiers will pass validation`() {
|
||||
val versionWithStableVersion = "android-mail@1.2.3-stable.1"
|
||||
val versionWithAlphaVersion = "android-mail@1.2.3-alpha.1"
|
||||
val versionWithRCVersion = "android-mail@1.2.3-RC.1"
|
||||
val versionWithBetaVersion = "android-mail@1.2.3-beta.1"
|
||||
assertTrue { validator.validate(versionWithStableVersion) }
|
||||
assertTrue { validator.validate(versionWithAlphaVersion) }
|
||||
assertTrue { validator.validate(versionWithRCVersion) }
|
||||
assertTrue { validator.validate(versionWithBetaVersion) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Version code with both version identifier and metadata will pass validation`() {
|
||||
val version = "android-mail@1.2.3-stable.1+some-metadata"
|
||||
assertTrue { validator.validate(version) }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class MockApiClient : ApiClient {
|
||||
var forceUpdated = false
|
||||
|
||||
override var shouldUseDoh = true
|
||||
override val appVersionHeader = "TestApp_1.0"
|
||||
override val appVersionHeader = "android-mail@1.2.3"
|
||||
override val userAgent = "Test/1.0 (Android 10; brand model)"
|
||||
|
||||
override val enableDebugLogging: Boolean = true
|
||||
|
||||
@@ -24,6 +24,7 @@ import okhttp3.mockwebserver.MockResponse
|
||||
import okhttp3.mockwebserver.MockWebServer
|
||||
import java.io.File
|
||||
import java.security.KeyStore
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.KeyManagerFactory
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
@@ -37,6 +38,8 @@ fun MockWebServer.prepareResponse(code: Int, body: String = "") {
|
||||
enqueue(response)
|
||||
}
|
||||
|
||||
fun MockWebServer.takeRequestWithDefaultTimeout() = takeRequest(10, TimeUnit.MILLISECONDS)
|
||||
|
||||
class TestTLSHelper {
|
||||
|
||||
var trustManagers: Array<TrustManager>
|
||||
|
||||
@@ -329,6 +329,10 @@ public final class me/proton/core/network/domain/client/ClientIdType$Companion {
|
||||
public final fun getMap ()Ljava/util/Map;
|
||||
}
|
||||
|
||||
public abstract interface class me/proton/core/network/domain/client/ClientVersionValidator {
|
||||
public abstract fun validate (Ljava/lang/String;)Z
|
||||
}
|
||||
|
||||
public final class me/proton/core/network/domain/client/CookieSessionId {
|
||||
public fun <init> (Ljava/lang/String;)V
|
||||
public final fun component1 ()Ljava/lang/String;
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Copyright (c) 2022 Proton Technologies AG
|
||||
* This file is part of Proton AG and ProtonCore.
|
||||
*
|
||||
* ProtonCore is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* ProtonCore is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.network.domain.client
|
||||
|
||||
interface ClientVersionValidator {
|
||||
fun validate(versionName: String?): Boolean
|
||||
}
|
||||
+3
-1
@@ -30,7 +30,9 @@ import me.proton.core.report.domain.entity.BugReportMeta
|
||||
import me.proton.core.report.domain.repository.ReportRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
public class ReportRepositoryImpl @Inject constructor(private val apiProvider: ApiProvider) : ReportRepository {
|
||||
public class ReportRepositoryImpl @Inject constructor(
|
||||
private val apiProvider: ApiProvider,
|
||||
) : ReportRepository {
|
||||
override suspend fun sendReport(
|
||||
bugReport: BugReport,
|
||||
meta: BugReportMeta,
|
||||
|
||||
+1
-1
@@ -53,7 +53,7 @@ internal class ReportRepositoryImplTest {
|
||||
email = "email@test"
|
||||
)
|
||||
private val testBugReportMeta = BugReportMeta(
|
||||
appVersionName = "1.2.3",
|
||||
appVersionName = "android-mail@1.2.3",
|
||||
clientName = "TestApp",
|
||||
osName = "Android",
|
||||
osVersion = "12",
|
||||
|
||||
@@ -30,7 +30,7 @@ internal val testBugReport = BugReport(
|
||||
email = "test@email"
|
||||
)
|
||||
internal val testBugReportMeta = BugReportMeta(
|
||||
appVersionName = "1.2.3",
|
||||
appVersionName = "android-mail@1.2.3",
|
||||
clientName = "TestApp",
|
||||
osName = "Android",
|
||||
osVersion = "100",
|
||||
|
||||
Reference in New Issue
Block a user