Added userId to fetchOrganizationKeys request.
MAILAND-1527
This commit is contained in:
parent
d806a4fce2
commit
ff38cd136a
|
@ -58,7 +58,8 @@ internal class CryptoTest {
|
|||
//region One Address Key Setup
|
||||
private val oneAddressKeyUserId = UserId("one_address_key_user")
|
||||
private val oneAddressKeyMailboxPassword = "7NgO4d0h72zt4XuFLOUbg352vhrn.tu"
|
||||
private val oneAddressKeyAddressId = "MMxTCP7DMErrWkOREQljdviitcZ1mDzjWcnzdg8wqObisClP25ILZ18vUsNgUVi-JD3O2EgmOuiEmx8C5qmofw=="
|
||||
private val oneAddressKeyAddressId =
|
||||
"MMxTCP7DMErrWkOREQljdviitcZ1mDzjWcnzdg8wqObisClP25ILZ18vUsNgUVi-JD3O2EgmOuiEmx8C5qmofw=="
|
||||
private val oneAddressKeyUserKeyFingerprint = "20cf363b58ec99e722e53ec411c31e8e5e07f4d0"
|
||||
private val armoredPrivateKey =
|
||||
"""
|
||||
|
@ -125,7 +126,7 @@ internal class CryptoTest {
|
|||
dlEt7f8XNvX3HxQw9w==
|
||||
=FW0u
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
""".trimIndent()
|
||||
""".trimIndent()
|
||||
private val oneAddressKeyAddressKeys = listOf(
|
||||
Keys(
|
||||
"DvRZxrFRFUnjsL6MCOdGjyMZ9AoECd7kNXX9uKxmV-75K4iArEHijRPvd7Dhw43yBlCDxIsbNSW7cg2Uu5FUFg==",
|
||||
|
@ -589,7 +590,7 @@ internal class CryptoTest {
|
|||
a2j5Ak+MfeXknuaYqP6hs5BzulMTTJjM
|
||||
=sm1p
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
""".trimIndent(),
|
||||
""".trimIndent(),
|
||||
0,
|
||||
1,
|
||||
null,
|
||||
|
@ -663,7 +664,7 @@ internal class CryptoTest {
|
|||
b5BlAx31rUZ+NdCZyPnU+83opOdYrRjy
|
||||
=kArG
|
||||
-----END PGP PRIVATE KEY BLOCK-----
|
||||
""".trimIndent(),
|
||||
""".trimIndent(),
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
|
@ -689,40 +690,40 @@ internal class CryptoTest {
|
|||
private val userKeyMapper = UserKeyBridgeMapper()
|
||||
|
||||
private val oneKeyUserMock: ch.protonmail.android.domain.entity.user.User = mockk {
|
||||
every { addresses } returns mockk {
|
||||
every { findBy(AddressId(oneAddressKeyAddressId)) } answers { addresses[1] }
|
||||
every { addresses } returns mapOf(
|
||||
1 to mockk {
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns oneAddressKeyAddressKeys.map(addressKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
every { addresses } returns mockk {
|
||||
every { findBy(AddressId(oneAddressKeyAddressId)) } answers { addresses[1] }
|
||||
every { addresses } returns mapOf(
|
||||
1 to mockk {
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns oneAddressKeyAddressKeys.map(addressKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
)
|
||||
}
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns oneAddressKeyUserKeys.map(userKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns oneAddressKeyUserKeys.map(userKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
}
|
||||
|
||||
private val manyAddressKeysUserMock: ch.protonmail.android.domain.entity.user.User = mockk {
|
||||
every { addresses } returns mockk {
|
||||
every { findBy(AddressId(manyAddressKeysAddressId)) } answers { addresses[1] }
|
||||
every { addresses } returns mapOf(
|
||||
1 to mockk {
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns manyAddressKeysAddressKeys.map(addressKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
every { addresses } returns mockk {
|
||||
every { findBy(AddressId(manyAddressKeysAddressId)) } answers { addresses[1] }
|
||||
every { addresses } returns mapOf(
|
||||
1 to mockk {
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns manyAddressKeysAddressKeys.map(addressKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
)
|
||||
}
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns manyAddressKeysUserKeys.map(userKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
every { keys } returns mockk {
|
||||
every { keys } returns manyAddressKeysUserKeys.map(userKeyMapper) { it.toNewModel() }
|
||||
every { primaryKey } returns keys.first()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
mockkStatic(TextUtils::class)
|
||||
|
@ -736,7 +737,11 @@ internal class CryptoTest {
|
|||
every { keys } returns oneAddressKeyAddressKeys
|
||||
}*/
|
||||
every { userManagerMock.getUserBlocking(oneAddressKeyUserId) } returns oneKeyUserMock
|
||||
every { userManagerMock.getUserPassphraseBlocking(oneAddressKeyUserId) } returns oneAddressKeyMailboxPassword.toByteArray()
|
||||
every {
|
||||
userManagerMock.getUserPassphraseBlocking(
|
||||
oneAddressKeyUserId
|
||||
)
|
||||
} returns oneAddressKeyMailboxPassword.toByteArray()
|
||||
|
||||
// many address keys
|
||||
/*
|
||||
|
@ -745,7 +750,11 @@ internal class CryptoTest {
|
|||
every { keys } returns manyAddressKeysAddressKeys
|
||||
}*/
|
||||
every { userManagerMock.getUserBlocking(manyAddressKeysUserId) } returns manyAddressKeysUserMock
|
||||
every { userManagerMock.getUserPassphraseBlocking(manyAddressKeysUserId) } returns manyAddressKeysMailboxPassword.toByteArray()
|
||||
every {
|
||||
userManagerMock.getUserPassphraseBlocking(
|
||||
manyAddressKeysUserId
|
||||
)
|
||||
} returns manyAddressKeysMailboxPassword.toByteArray()
|
||||
|
||||
// token and signature generation
|
||||
every { userManagerMock.currentUserId } returns tokenAndSignatureUserId
|
||||
|
@ -1609,7 +1618,7 @@ internal class CryptoTest {
|
|||
every { mimeType } returns "text/rfc822-headers; protected-headers=\"v1\""
|
||||
every { fileSize } returns 48
|
||||
every { messageId } returns mockedMessageId
|
||||
//every { headers } returns
|
||||
// every { headers } returns
|
||||
},
|
||||
mockk {
|
||||
every { attachmentId } returns "PGPAttachment/0-KKFldtlrtWQRGmOQR1V4QVsnqq7nuHw_nkmdDAd2xtIvibEqnV0IYVS3FfX-RT8BruIrL35HQ35rQkP6VbBg==/778b4f3c8e74a652aefbee588e67421a/1"
|
||||
|
@ -1617,7 +1626,7 @@ internal class CryptoTest {
|
|||
every { mimeType } returns "image/jpeg; name=\"elon.jpg\""
|
||||
every { fileSize } returns 27723
|
||||
every { messageId } returns mockedMessageId
|
||||
//every { headers } returns
|
||||
// every { headers } returns
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -1652,8 +1661,12 @@ internal class CryptoTest {
|
|||
|
||||
@Test
|
||||
fun check_key_passphrase() {
|
||||
assertTrue(openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, oneAddressKeyMailboxPassword.toByteArray()))
|
||||
assertFalse(openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, "incorrect key password".toByteArray()))
|
||||
assertTrue(
|
||||
openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, oneAddressKeyMailboxPassword.toByteArray())
|
||||
)
|
||||
assertFalse(
|
||||
openPgp.checkPassphrase(oneAddressKeyAddressKeys[0].privateKey, "incorrect key password".toByteArray())
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1704,7 +1717,7 @@ internal class CryptoTest {
|
|||
every { userManagerMock.getCurrentUserPassphrase() } returns passphrase
|
||||
runBlocking {
|
||||
val (token, signature) =
|
||||
GenerateTokenAndSignature(userManagerMock, openPgpMock).invoke(null)
|
||||
GenerateTokenAndSignature(userManagerMock, openPgpMock).invoke("")
|
||||
val testMessage = newPGPMessageFromArmored(token)
|
||||
val testKey = newKeyFromArmored(armoredPrivateKey)
|
||||
val unlocked = testKey.unlock(passphrase)
|
||||
|
@ -1714,7 +1727,9 @@ internal class CryptoTest {
|
|||
assertEquals(randomTokenString, decryptedTokenString)
|
||||
val armoredSignature = newPGPSignatureFromArmored(signature)
|
||||
|
||||
verificationKeyRing.verifyDetached(decryptedTokenPlainMessage, armoredSignature, com.proton.gopenpgp.crypto.Crypto.getUnixTime())
|
||||
verificationKeyRing.verifyDetached(
|
||||
decryptedTokenPlainMessage, armoredSignature, com.proton.gopenpgp.crypto.Crypto.getUnixTime()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,10 +32,10 @@ import ch.protonmail.android.api.models.DeleteResponse
|
|||
import ch.protonmail.android.api.models.DraftBody
|
||||
import ch.protonmail.android.api.models.GetSubscriptionResponse
|
||||
import ch.protonmail.android.api.models.IDList
|
||||
import ch.protonmail.android.api.models.Keys
|
||||
import ch.protonmail.android.api.models.LabelBody
|
||||
import ch.protonmail.android.api.models.MailSettingsResponse
|
||||
import ch.protonmail.android.api.models.MoveToFolderResponse
|
||||
import ch.protonmail.android.api.models.OrganizationKeysResponse
|
||||
import ch.protonmail.android.api.models.OrganizationResponse
|
||||
import ch.protonmail.android.api.models.PaymentMethodsResponse
|
||||
import ch.protonmail.android.api.models.PaymentsStatusResponse
|
||||
|
@ -312,7 +312,8 @@ class ProtonMailApiManager @Inject constructor(var api: ProtonMailApi) :
|
|||
override suspend fun fetchOrganization(userId: UserId): ApiResult<OrganizationResponse> =
|
||||
api.fetchOrganization(userId)
|
||||
|
||||
override suspend fun fetchOrganizationKeys(): ApiResult<Keys> = api.fetchOrganizationKeys()
|
||||
override suspend fun fetchOrganizationKeys(userId: UserId): ApiResult<OrganizationKeysResponse> =
|
||||
api.fetchOrganizationKeys(userId)
|
||||
|
||||
override suspend fun fetchSubscription(): GetSubscriptionResponse = api.fetchSubscription()
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* Copyright (c) 2020 Proton Technologies AG
|
||||
*
|
||||
* This file is part of ProtonMail.
|
||||
*
|
||||
* ProtonMail 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.
|
||||
*
|
||||
* ProtonMail 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 ProtonMail. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package ch.protonmail.android.api.models
|
||||
|
||||
import ch.protonmail.android.api.utils.Fields
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class OrganizationKeysResponse(
|
||||
@SerialName(Fields.Response.CODE)
|
||||
val code: Int,
|
||||
|
||||
@SerialName(Fields.Keys.KeyBody.PUBLIC_KEY)
|
||||
val publicKey: String,
|
||||
|
||||
@SerialName(Fields.Keys.KeyBody.PRIVATE_KEY)
|
||||
val privateKey: String,
|
||||
)
|
|
@ -26,7 +26,7 @@ import androidx.work.WorkManager
|
|||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import ch.protonmail.android.api.ProtonMailApiManager
|
||||
import ch.protonmail.android.api.models.Keys
|
||||
import ch.protonmail.android.api.models.OrganizationKeysResponse
|
||||
import ch.protonmail.android.core.UserManager
|
||||
import ch.protonmail.android.domain.entity.user.Address
|
||||
import ch.protonmail.android.domain.entity.user.AddressKey
|
||||
|
@ -81,24 +81,25 @@ class AddressKeyActivationWorker @AssistedInject constructor(
|
|||
// get orgKeys if existent -> This will work only if user is an organisation owner, otherwise
|
||||
// backend will return 403 for all other users (e.g. just members of an organisation)
|
||||
val orgKeysResult = if (context.app.organization != null) {
|
||||
api.fetchOrganizationKeys()
|
||||
api.fetchOrganizationKeys(UserId(user.name.s))
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val orgKeys = if (orgKeysResult is ApiResult.Success) {
|
||||
orgKeysResult.value
|
||||
val errors = if (orgKeysResult is ApiResult.Success) {
|
||||
val keysResponse = orgKeysResult.value
|
||||
|
||||
keysToActivate.map {
|
||||
activateKey(it, user.keys, mailboxPassword, keysResponse, it in primaryKeys)
|
||||
}.filterIsInstance<ActivationResult.Error>()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val errors = keysToActivate.map {
|
||||
activateKey(it, user.keys, mailboxPassword, orgKeys, it in primaryKeys)
|
||||
}.filterIsInstance<ActivationResult.Error>()
|
||||
val failedCountMessage = "impossible to activate ${errors.size} keys out of ${keysToActivate.size}"
|
||||
val failedCountMessage = "impossible to activate ${errors?.size} keys out of ${keysToActivate.size}"
|
||||
|
||||
return@withContext when {
|
||||
errors.isEmpty() -> Result.success()
|
||||
errors.isNullOrEmpty() -> Result.success()
|
||||
errors.any { it is ActivationResult.Error.Network } -> {
|
||||
Timber.w("Network error: $failedCountMessage, runAttemptCount: $runAttemptCount")
|
||||
if (runAttemptCount < MAX_RETRY_COUNT) {
|
||||
|
@ -118,7 +119,7 @@ class AddressKeyActivationWorker @AssistedInject constructor(
|
|||
addressKey: AddressKey,
|
||||
userKeys: UserKeys,
|
||||
mailboxPassword: ByteArray,
|
||||
orgKeys: Keys?,
|
||||
orgKeysResponse: OrganizationKeysResponse,
|
||||
isPrimary: Boolean
|
||||
): ActivationResult {
|
||||
return try {
|
||||
|
@ -163,7 +164,7 @@ class AddressKeyActivationWorker @AssistedInject constructor(
|
|||
} else {
|
||||
Timber.v("Activate key for non-legacy user")
|
||||
val generatedTokenAndSignature =
|
||||
GenerateTokenAndSignature(userManager, openPgp).invoke(orgKeys?.toUserKey())
|
||||
GenerateTokenAndSignature(userManager, openPgp).invoke(orgKeysResponse.privateKey)
|
||||
val keyActivationBody = KeyActivationBody(
|
||||
newPrivateKey,
|
||||
signedKeyList,
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
package ch.protonmail.android.api.segments.organization
|
||||
|
||||
import ch.protonmail.android.api.models.Keys
|
||||
import ch.protonmail.android.api.models.OrganizationKeysResponse
|
||||
import ch.protonmail.android.api.models.OrganizationResponse
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
|
@ -31,8 +31,8 @@ class OrganizationApi(private val apiProvider: ApiProvider) : OrganizationApiSpe
|
|||
fetchOrganization()
|
||||
}
|
||||
|
||||
override suspend fun fetchOrganizationKeys(): ApiResult<Keys> =
|
||||
apiProvider.get<OrganizationService>().invoke {
|
||||
override suspend fun fetchOrganizationKeys(userId: UserId): ApiResult<OrganizationKeysResponse> =
|
||||
apiProvider.get<OrganizationService>(userId).invoke {
|
||||
fetchOrganizationsKeys()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
package ch.protonmail.android.api.segments.organization
|
||||
|
||||
import ch.protonmail.android.api.models.Keys
|
||||
import ch.protonmail.android.api.models.OrganizationKeysResponse
|
||||
import ch.protonmail.android.api.models.OrganizationResponse
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
|
@ -27,6 +27,6 @@ interface OrganizationApiSpec {
|
|||
|
||||
suspend fun fetchOrganization(userId: UserId): ApiResult<OrganizationResponse>
|
||||
|
||||
suspend fun fetchOrganizationKeys(): ApiResult<Keys>
|
||||
suspend fun fetchOrganizationKeys(userId: UserId): ApiResult<OrganizationKeysResponse>
|
||||
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
*/
|
||||
package ch.protonmail.android.api.segments.organization
|
||||
|
||||
import ch.protonmail.android.api.models.Keys
|
||||
import ch.protonmail.android.api.models.OrganizationKeysResponse
|
||||
import ch.protonmail.android.api.models.OrganizationResponse
|
||||
import ch.protonmail.android.api.segments.RetrofitConstants.ACCEPT_HEADER_V1
|
||||
import ch.protonmail.android.api.segments.RetrofitConstants.CONTENT_TYPE
|
||||
|
@ -34,6 +34,6 @@ interface OrganizationService : BaseRetrofitApi {
|
|||
|
||||
@GET("organizations/keys")
|
||||
@Headers(CONTENT_TYPE, ACCEPT_HEADER_V1)
|
||||
suspend fun fetchOrganizationsKeys(): Keys
|
||||
suspend fun fetchOrganizationsKeys(): OrganizationKeysResponse
|
||||
|
||||
}
|
||||
|
|
|
@ -361,6 +361,7 @@ object Fields {
|
|||
object KeyBody {
|
||||
const val FLAGS = "Flags"
|
||||
const val PUBLIC_KEY = "PublicKey"
|
||||
const val PRIVATE_KEY = "PrivateKey"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,10 +35,11 @@ class GetOrganizationJob : ProtonMailBaseJob(
|
|||
@Throws(Throwable::class)
|
||||
override fun onRun() {
|
||||
runBlocking {
|
||||
val response = getApi().fetchOrganization(requireNotNull(userId))
|
||||
val userId = requireNotNull(userId)
|
||||
val response = getApi().fetchOrganization(userId)
|
||||
val keysResponse = if (response is ApiResult.Success) {
|
||||
Timber.v("fetching Organization Keys")
|
||||
getApi().fetchOrganizationKeys()
|
||||
getApi().fetchOrganizationKeys(userId)
|
||||
} else {
|
||||
Timber.i("Get Organization failure: $response")
|
||||
null
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
package ch.protonmail.android.usecase.crypto
|
||||
|
||||
import ch.protonmail.android.core.UserManager
|
||||
import ch.protonmail.android.domain.entity.user.UserKey
|
||||
import ch.protonmail.android.utils.crypto.OpenPGP
|
||||
import com.proton.gopenpgp.crypto.Crypto
|
||||
import javax.inject.Inject
|
||||
|
@ -29,7 +28,7 @@ class GenerateTokenAndSignature @Inject constructor (
|
|||
private val userManager: UserManager,
|
||||
private val openPgp: OpenPGP
|
||||
) {
|
||||
operator fun invoke(orgKeys: UserKey?): TokenAndSignature {
|
||||
operator fun invoke(privateKey: String): TokenAndSignature {
|
||||
val user = userManager.currentUser
|
||||
val secret = openPgp.randomToken()
|
||||
val tokenString = secret.joinToString("") { String.format("%02x", (it.toInt() and 0xff)) }
|
||||
|
@ -39,10 +38,8 @@ class GenerateTokenAndSignature @Inject constructor (
|
|||
val unlockedUserKey = Crypto.newKeyFromArmored(armoredPrivateKey).unlock(mailboxPassword)
|
||||
val tokenKeyRing = Crypto.newKeyRing(unlockedUserKey)
|
||||
|
||||
if (orgKeys != null) {
|
||||
val unlockedOrgKey = Crypto.newKeyFromArmored(orgKeys.privateKey.content.s).unlock(mailboxPassword)
|
||||
tokenKeyRing.addKey(unlockedOrgKey)
|
||||
}
|
||||
val unlockedOrgKey = Crypto.newKeyFromArmored(privateKey).unlock(mailboxPassword)
|
||||
tokenKeyRing.addKey(unlockedOrgKey)
|
||||
|
||||
val token = tokenKeyRing.encrypt(binMessage, null).armored
|
||||
val signature = tokenKeyRing.signDetached(binMessage).armored
|
||||
|
|
Loading…
Reference in New Issue