From d58e0a751e3801ac8feb1f3df62d2f77aa68ba39 Mon Sep 17 00:00:00 2001 From: Neil Marietta Date: Thu, 31 Dec 2020 13:12:28 +0100 Subject: [PATCH] Added User/Address Domain module. --- gradle/plugin/src/main/kotlin/modules.kt | 6 + .../proton/core/key/domain/KeyHolderCrypto.kt | 12 ++ .../core/key/domain/entity/key/PrivateKey.kt | 9 +- .../extension/KeyHolderPrivateKeyList.kt | 7 ++ user/build.gradle.kts | 36 ++++++ user/domain/build.gradle.kts | 44 +++++++ .../proton/core/user/domain/UnlockResult.kt | 28 +++++ .../me/proton/core/user/domain/UserManager.kt | 106 ++++++++++++++++ .../me/proton/core/user/domain/entity/User.kt | 118 ++++++++++++++++++ .../core/user/domain/entity/UserAddress.kt | 83 ++++++++++++ .../core/user/domain/entity/UserAddressKey.kt | 36 ++++++ .../proton/core/user/domain/entity/UserKey.kt | 33 +++++ .../core/user/domain/entity/UserType.kt | 36 ++++++ .../user/domain/extension/UserAddressKey.kt | 27 ++++ .../user/domain/extension/UserAddressList.kt | 65 ++++++++++ .../core/user/domain/extension/UserPlan.kt | 33 +++++ .../domain/repository/PassphraseRepository.kt | 39 ++++++ .../repository/UserAddressRepository.kt | 57 +++++++++ .../user/domain/repository/UserRepository.kt | 40 ++++++ .../core/user/domain/UserKeysApiGoal.kt | 82 ++++++++++++ user/src/main/AndroidManifest.xml | 19 +++ 21 files changed, 915 insertions(+), 1 deletion(-) create mode 100644 user/build.gradle.kts create mode 100644 user/domain/build.gradle.kts create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/UnlockResult.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/UserManager.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/entity/User.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddress.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddressKey.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserKey.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserType.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressKey.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressList.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserPlan.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/repository/PassphraseRepository.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserAddressRepository.kt create mode 100644 user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserRepository.kt create mode 100644 user/domain/src/test/kotlin/me/proton/core/user/domain/UserKeysApiGoal.kt create mode 100644 user/src/main/AndroidManifest.xml diff --git a/gradle/plugin/src/main/kotlin/modules.kt b/gradle/plugin/src/main/kotlin/modules.kt index a0a191b37..ea40f096d 100644 --- a/gradle/plugin/src/main/kotlin/modules.kt +++ b/gradle/plugin/src/main/kotlin/modules.kt @@ -80,6 +80,12 @@ object Module { const val keyDomain = "$key:key-domain" const val keyData = "$key:key-data" + // User + const val user = ":user" + const val userDomain = "$user:user-domain" + const val userPresentation = "$user:user-presentation" + const val userData = "$user:user-data" + // Contacts const val contacts = ":contacts" const val contactsDomain = "$contacts:contacts-domain" diff --git a/key/domain/src/main/kotlin/me/proton/core/key/domain/KeyHolderCrypto.kt b/key/domain/src/main/kotlin/me/proton/core/key/domain/KeyHolderCrypto.kt index ebf718a75..0585a45c8 100644 --- a/key/domain/src/main/kotlin/me/proton/core/key/domain/KeyHolderCrypto.kt +++ b/key/domain/src/main/kotlin/me/proton/core/key/domain/KeyHolderCrypto.kt @@ -36,6 +36,18 @@ import me.proton.core.key.domain.entity.keyholder.KeyHolderContext * Executes the given [block] function for a [KeyHolder] on a [KeyHolderContext] and then close any associated * key resources whether an exception is thrown or not. * + * Example: + * ``` + * keyholder.useKeys(context) { + * val text = "text" + * + * val encryptedText = encryptText(text) + * val signedText = signText(text) + * + * val decryptedText = decryptText(encryptedText) + * val isVerified = verifyText(decryptedText, signedText) + * } + * ``` * @param context [CryptoContext] providing any needed dependencies for Crypto functions. * @param block a function allowing usage of [KeyHolderContext] extension functions. * @return the result of [block] function invoked on this [KeyHolder]. diff --git a/key/domain/src/main/kotlin/me/proton/core/key/domain/entity/key/PrivateKey.kt b/key/domain/src/main/kotlin/me/proton/core/key/domain/entity/key/PrivateKey.kt index cf8376ad8..71e491efb 100644 --- a/key/domain/src/main/kotlin/me/proton/core/key/domain/entity/key/PrivateKey.kt +++ b/key/domain/src/main/kotlin/me/proton/core/key/domain/entity/key/PrivateKey.kt @@ -25,4 +25,11 @@ data class PrivateKey( val key: Armored, val isPrimary: Boolean, internal val passphrase: EncryptedByteArray? -) +) { + /** + * True if no passphrase is associated, thereby only public crypto functions are available. + * + * False if a passphrase is associated, thereby public and private crypto functions are available. + */ + val isLocked = passphrase == null +} diff --git a/key/domain/src/main/kotlin/me/proton/core/key/domain/extension/KeyHolderPrivateKeyList.kt b/key/domain/src/main/kotlin/me/proton/core/key/domain/extension/KeyHolderPrivateKeyList.kt index e04850ef3..d6fec1e6f 100644 --- a/key/domain/src/main/kotlin/me/proton/core/key/domain/extension/KeyHolderPrivateKeyList.kt +++ b/key/domain/src/main/kotlin/me/proton/core/key/domain/extension/KeyHolderPrivateKeyList.kt @@ -21,3 +21,10 @@ package me.proton.core.key.domain.extension import me.proton.core.key.domain.entity.keyholder.KeyHolderPrivateKey fun List.primary(): KeyHolderPrivateKey? = firstOrNull { it.privateKey.isPrimary } + +/** + * True if no passphrase is associated with any keys, thereby only public crypto functions are available. + * + * False if at least one passphrase is associated, thereby public and private crypto functions are available. + */ +fun List.areAllLocked(): Boolean = all { it.privateKey.isLocked } diff --git a/user/build.gradle.kts b/user/build.gradle.kts new file mode 100644 index 000000000..50fda659b --- /dev/null +++ b/user/build.gradle.kts @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +import studio.forface.easygradle.dsl.* + +plugins { + id("com.android.library") + kotlin("android") +} + +//libVersion = Version(0, 1, 0) + +android() + +dependencies { + api( + //project(Module.userPresentation), + project(Module.userDomain) + //project(Module.userData) + ) +} diff --git a/user/domain/build.gradle.kts b/user/domain/build.gradle.kts new file mode 100644 index 000000000..4e0958872 --- /dev/null +++ b/user/domain/build.gradle.kts @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +import studio.forface.easygradle.dsl.* + +plugins { + `java-library` + kotlin("jvm") +} + +//libVersion = Version(0, 1, 0) + +dependencies { + implementation( + + project(Module.kotlinUtil), + project(Module.cryptoCommon), + project(Module.domain), + + // Feature + project(Module.keyDomain), + + // Kotlin + `kotlin-jdk8`, + `coroutines-core` + ) + + testImplementation(project(Module.kotlinTest)) +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/UnlockResult.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/UnlockResult.kt new file mode 100644 index 000000000..b4d8e1751 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/UnlockResult.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain + +sealed class UnlockResult { + object Success : UnlockResult() + sealed class Error : UnlockResult() { + object NoPrimaryKey : Error() + object NoKeySaltsForPrimaryKey : Error() + object PrimaryKeyInvalidPassphrase : Error() + } +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/UserManager.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/UserManager.kt new file mode 100644 index 000000000..75497f83c --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/UserManager.kt @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain + +import kotlinx.coroutines.flow.Flow +import me.proton.core.crypto.common.simple.EncryptedByteArray +import me.proton.core.crypto.common.simple.PlainByteArray +import me.proton.core.domain.arch.DataResult +import me.proton.core.domain.entity.SessionUserId +import me.proton.core.domain.entity.UserId +import me.proton.core.key.domain.extension.areAllLocked +import me.proton.core.user.domain.entity.User +import me.proton.core.user.domain.entity.UserAddress + +interface UserManager { + + /** + * Get [User], using [sessionUserId]. + * + * @return value emitted from cache/disk, then from fetcher if [refresh] is true. + */ + fun getUser( + sessionUserId: SessionUserId, + refresh: Boolean = false + ): Flow> + + /** + * Get all [UserAddress], using [sessionUserId]. + * + * @return value emitted from cache/disk, then from fetcher if [refresh] is true. + */ + fun getAddresses( + sessionUserId: SessionUserId, + refresh: Boolean = false + ): Flow>> + + /** + * Try to unlock the user with the given [password]. + * + * On [UnlockResult.Success], the passphrase, derived from password, is stored and the [User] keys ready to be used. + * + * @param userId [UserId] to unlock. + * @param password [PlainByteArray] to use to unlock. + * @param refresh if false, use data from cache/disk, otherwise data are refreshed before proceeding. + * + * @see [User.keys] + * @see [areAllLocked] + */ + suspend fun unlockWithPassword( + userId: UserId, + password: PlainByteArray, + refresh: Boolean = false + ): UnlockResult + + /** + * Try to unlock the user with the given [passphrase]. + * + * On [UnlockResult.Success], the passphrase is stored and the [User] keys ready to be used. + * + * @param userId [UserId] to unlock. + * @param passphrase [EncryptedByteArray] to use to unlock. + * @param refresh if false, use data from cache/disk, otherwise data is refreshed before proceeding. + * + * @see [User.keys] + * @see [areAllLocked] + */ + suspend fun unlockWithPassphrase( + userId: UserId, + passphrase: EncryptedByteArray, + refresh: Boolean = false + ): UnlockResult + + /** + * Lock the User Keys. + * + * The passphrase is cleared, and the User Keys not anymore ready to be used. + */ + suspend fun lock(userId: UserId) + + /** + * Change password for a [userId]. + * + * Note: This function takes care to adapt any needed passphrase or key. + */ + suspend fun changePassword( + userId: UserId, + oldPassword: String, + newPassword: String + ) +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/User.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/User.kt new file mode 100644 index 000000000..201a6c2d0 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/User.kt @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.entity + +import me.proton.core.domain.entity.UserId +import me.proton.core.key.domain.entity.keyholder.KeyHolder +import me.proton.core.key.domain.extension.areAllLocked +import me.proton.core.key.domain.useKeys +import me.proton.core.user.domain.UserManager +import me.proton.core.user.domain.extension.hasServiceForMail +import me.proton.core.user.domain.extension.hasServiceForVpn +import me.proton.core.user.domain.extension.hasSubscriptionForMail +import me.proton.core.user.domain.extension.hasSubscriptionForVpn + +/** + * Represent an authenticated User. + * + * [User] without valid Session is removed from local persistence. + */ +data class User( + val userId: UserId, + /** Optional email address. */ + val email: String?, + /** Optional name. */ + val name: String?, + /** Optional display name. */ + val displayName: String?, + /** Currency expressed in ISO 4217 (3 letters). */ + val currency: String, + /** Monetary credits. This value is affected by [currency]. */ + val credit: Int, + /** Used space size in Bytes. */ + val usedSpace: Long, + /** Max space size in Bytes. */ + val maxSpace: Long, + /** Max upload size in Bytes. */ + val maxUpload: Long, + /** Organization member role, if any. */ + val role: Role?, + /** Whether the user controls their own keys or not. All free users are private. */ + val private: Boolean, + /** + * Services flags. + * + * @see [hasServiceForMail] + * @see [hasServiceForVpn] + */ + val services: Int, + /** + * Subscription flags. + * + * @see [hasSubscriptionForMail] + * @see [hasSubscriptionForVpn] + */ + val subscribed: Int, + /** Invoice delinquent. */ + val delinquent: Delinquent?, + /** + * User Private Keys used by crypto functions (e.g. encrypt, decrypt, sign, verify). + * + * Example: + * ``` + * user.useKeys(context) { + * val text = "text" + * + * val encryptedText = encryptText(text) + * val signedText = signText(text) + * + * val decryptedText = decryptText(encryptedText) + * val isVerified = verifyText(decryptedText, signedText) + * } + * ``` + * @see [useKeys] + * @see [areAllLocked] + * @see [UserManager.unlockWithPassword] + * @see [UserManager.unlockWithPassphrase] + * @see [UserManager.lock] + * */ + override val keys: List +) : KeyHolder + +enum class Delinquent(val value: Int) { + None(0), + InvoiceAvailable(1), + InvoiceOverdue(2), + InvoiceDelinquent(3), + InvoiceMailDisabled(4); + + companion object { + val map = values().associateBy { it.value } + } +} + +enum class Role(val value: Int) { + NoOrganization(0), + OrganizationMember(1), + OrganizationAdmin(2); + + companion object { + val map = values().associateBy { it.value } + } +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddress.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddress.kt new file mode 100644 index 000000000..f26b722dc --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddress.kt @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.entity + +import me.proton.core.domain.entity.UserId +import me.proton.core.key.domain.entity.keyholder.KeyHolder +import me.proton.core.key.domain.extension.areAllLocked +import me.proton.core.key.domain.useKeys +import me.proton.core.user.domain.UserManager + +data class AddressId(val id: String) + +data class UserAddress( + val userId: UserId, + val addressId: AddressId, + val email: String, + val displayName: String? = null, + val domainId: String? = null, + val canSend: Boolean, + val canReceive: Boolean, + val enabled: Boolean, + val type: AddressType? = null, + val order: Int, + /** + * Address Private Keys used by crypto functions (e.g. encrypt, decrypt, sign, verify). + * + * Example: + * ``` + * userAddress.useKeys(context) { + * val text = "text" + * + * val encryptedText = encryptText(text) + * val signedText = signText(text) + * + * val decryptedText = decryptText(encryptedText) + * val isVerified = verifyText(decryptedText, signedText) + * } + * ``` + * @see [useKeys] + * @see [areAllLocked] + * @see [UserManager.unlockWithPassword] + * @see [UserManager.unlockWithPassphrase] + * @see [UserManager.lock] + * */ + override val keys: List +) : KeyHolder + +enum class AddressType(val value: Int) { + /** First address the user created using a ProtonMail domain. */ + Original(1), + + /** Subsequent addresses created using a ProtonMail domain. */ + Alias(2), + + /** Custom domain address. */ + Custom(3), + + /** Premium "pm.me" domain. */ + Premium(4), + + /** External address. */ + External(5); + + companion object { + val map = values().associateBy { it.value } + } +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddressKey.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddressKey.kt new file mode 100644 index 000000000..d05bec264 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserAddressKey.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.entity + +import me.proton.core.crypto.common.pgp.Armored +import me.proton.core.key.domain.entity.key.KeyId +import me.proton.core.key.domain.entity.key.PrivateKey +import me.proton.core.key.domain.entity.keyholder.KeyHolderPrivateKey + +data class UserAddressKey( + val addressId: AddressId, + val version: Int, + val flags: Int, + val token: Armored? = null, + val signature: Armored? = null, + val activation: Armored? = null, + val active: Boolean, + override val keyId: KeyId, + override val privateKey: PrivateKey +) : KeyHolderPrivateKey diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserKey.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserKey.kt new file mode 100644 index 000000000..c07bfaafc --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserKey.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.entity + +import me.proton.core.crypto.common.pgp.Armored +import me.proton.core.domain.entity.UserId +import me.proton.core.key.domain.entity.key.KeyId +import me.proton.core.key.domain.entity.key.PrivateKey +import me.proton.core.key.domain.entity.keyholder.KeyHolderPrivateKey + +data class UserKey( + val userId: UserId, + val version: Int, + val activation: Armored? = null, + override val keyId: KeyId, + override val privateKey: PrivateKey, +) : KeyHolderPrivateKey diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserType.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserType.kt new file mode 100644 index 000000000..9ef733e70 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/entity/UserType.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.entity + +enum class UserType { + /** + * User with no email addresses associated with it. + */ + Username, + + /** + * User with at least one internal email address associated with it. + */ + Internal, + + /** + * User with at least one external email address associated with it. + */ + External +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressKey.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressKey.kt new file mode 100644 index 000000000..9eeebc324 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressKey.kt @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.extension + +import me.proton.core.user.domain.entity.UserAddressKey + +private const val MASK_CAN_VERIFY_SIGNATURE = 1 // 01 +private const val MASK_CAN_ENCRYPT_VALUE = 2 // 10 + +fun UserAddressKey.canEncrypt(): Boolean = flags.and(MASK_CAN_ENCRYPT_VALUE) == MASK_CAN_ENCRYPT_VALUE +fun UserAddressKey.canVerifySignature(): Boolean = flags.and(MASK_CAN_VERIFY_SIGNATURE) == MASK_CAN_VERIFY_SIGNATURE diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressList.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressList.kt new file mode 100644 index 000000000..7937ed8c3 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserAddressList.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.extension + +import me.proton.core.user.domain.entity.UserType +import me.proton.core.user.domain.entity.AddressType +import me.proton.core.user.domain.entity.UserAddress + +/** + * @return primary [UserAddress] (with lower [UserAddress.order]). + */ +fun List.primary() = minByOrNull { it.order } + +/** + * @return [List] of [UserAddress] sorted by [UserAddress.order]. + */ +fun List.sorted() = sortedBy { it.order } + +/** + * Checks if all addresses are of type [AddressType.External]. + */ +fun List.allExternal() = all { it.type == AddressType.External } + +/** + * Returns `true` if the account is of type [UserType.Username]. + */ +fun List.usernameOnly() = isEmpty() + +/** + * Client supplies the minimal [userType] it needs to operate. The result is if current account satisfies the + * required account. + */ +fun List.satisfiesUserType(userType: UserType): Boolean = when (userType) { + // If client needs Username account, then it should be fine with any account type. + UserType.Username -> true + // If client needs External account, we return true only if current account is External or Internal. + UserType.External -> !usernameOnly() + // If client needs Internal only account to operate, we return true if current account is Internal only. + UserType.Internal -> !usernameOnly() && !allExternal() +} + +/** + * Determines and returns current [UserType]. + */ +fun List.currentUserType(): UserType = when { + usernameOnly() -> UserType.Username + allExternal() -> UserType.External + else -> UserType.Internal +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserPlan.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserPlan.kt new file mode 100644 index 000000000..139cca8b4 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/extension/UserPlan.kt @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.extension + +import me.proton.core.user.domain.entity.User + +private const val MASK_MAIL = 1 // 0001 +private const val MASK_VPN = 4 // 0100 + +private fun User.hasServiceFor(mask: Int): Boolean = mask.and(services) == mask +private fun User.hasSubscriptionFor(mask: Int): Boolean = mask.and(subscribed) == mask + +fun User.hasServiceForMail(): Boolean = hasServiceFor(MASK_MAIL) +fun User.hasServiceForVpn(): Boolean = hasServiceFor(MASK_VPN) + +fun User.hasSubscriptionForMail(): Boolean = hasSubscriptionFor(MASK_MAIL) +fun User.hasSubscriptionForVpn(): Boolean = hasSubscriptionFor(MASK_VPN) diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/PassphraseRepository.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/PassphraseRepository.kt new file mode 100644 index 000000000..dfc75dc53 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/PassphraseRepository.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.repository + +import me.proton.core.crypto.common.simple.EncryptedByteArray +import me.proton.core.domain.entity.UserId + +interface PassphraseRepository { + /** + * Set encrypted [passphrase] for a [userId]. + */ + suspend fun setPassphrase(userId: UserId, passphrase: EncryptedByteArray) + + /** + * Get encrypted passphrase for a [userId], if exist, or `null` otherwise. + */ + suspend fun getPassphrase(userId: UserId): EncryptedByteArray? + + /** + * Clear passphrase for a [userId]. + */ + suspend fun clearPassphrase(userId: UserId) +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserAddressRepository.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserAddressRepository.kt new file mode 100644 index 000000000..7e2e42ce0 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserAddressRepository.kt @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.repository + +import kotlinx.coroutines.flow.Flow +import me.proton.core.domain.arch.DataResult +import me.proton.core.domain.entity.SessionUserId +import me.proton.core.user.domain.entity.AddressId +import me.proton.core.user.domain.entity.UserAddress + +interface UserAddressRepository { + /** + * Get all [UserAddress], using [sessionUserId]. + * + * @return value emitted from cache/disk, then from fetcher if [refresh] is true. + */ + fun getAddresses(sessionUserId: SessionUserId, refresh: Boolean = false): Flow>> + + /** + * Get all [UserAddress], using [sessionUserId]. + * + * @return value from cache/disk if [refresh] is false, otherwise from fetcher if [refresh] is true. + */ + suspend fun getAddressesBlocking(sessionUserId: SessionUserId, refresh: Boolean = false): List + + /** + * Get [UserAddress], by [addressId], using [sessionUserId]. + * + * @return value from cache/disk if [refresh] is false, otherwise from fetcher if [refresh] is true. + */ + suspend fun getAddressBlocking( + sessionUserId: SessionUserId, + addressId: AddressId, + refresh: Boolean = false + ): UserAddress? + + /** + * Setup new non-sub user [UserAddress] with [displayName] and [domain], remotely. + */ + suspend fun setupAddress(sessionUserId: SessionUserId, displayName: String, domain: String): UserAddress +} diff --git a/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserRepository.kt b/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserRepository.kt new file mode 100644 index 000000000..db2a526b8 --- /dev/null +++ b/user/domain/src/main/kotlin/me/proton/core/user/domain/repository/UserRepository.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain.repository + +import kotlinx.coroutines.flow.Flow +import me.proton.core.domain.arch.DataResult +import me.proton.core.domain.entity.SessionUserId +import me.proton.core.user.domain.entity.User + +interface UserRepository { + /** + * Get [User], using [sessionUserId]. + * + * @return value emitted from cache/disk, then from fetcher if [refresh] is true. + */ + fun getUser(sessionUserId: SessionUserId, refresh: Boolean = false): Flow> + + /** + * Get [User], using [sessionUserId]. + * + * @return value from cache/disk if [refresh] is false, otherwise from fetcher if [refresh] is true. + */ + suspend fun getUserBlocking(sessionUserId: SessionUserId, refresh: Boolean = false): User +} diff --git a/user/domain/src/test/kotlin/me/proton/core/user/domain/UserKeysApiGoal.kt b/user/domain/src/test/kotlin/me/proton/core/user/domain/UserKeysApiGoal.kt new file mode 100644 index 000000000..12cfe82cd --- /dev/null +++ b/user/domain/src/test/kotlin/me/proton/core/user/domain/UserKeysApiGoal.kt @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2020 Proton Technologies AG + * This file is part of Proton Technologies 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 . + */ + +package me.proton.core.user.domain + +import me.proton.core.crypto.common.context.CryptoContext +import me.proton.core.key.domain.decryptAndVerifyData +import me.proton.core.key.domain.decryptAndVerifyDataOrNull +import me.proton.core.key.domain.decryptAndVerifyText +import me.proton.core.key.domain.decryptAndVerifyTextOrNull +import me.proton.core.key.domain.decryptText +import me.proton.core.key.domain.decryptTextOrNull +import me.proton.core.key.domain.encryptAndSignData +import me.proton.core.key.domain.encryptAndSignText +import me.proton.core.key.domain.encryptText +import me.proton.core.key.domain.signText +import me.proton.core.key.domain.useKeys +import me.proton.core.key.domain.verifyText +import me.proton.core.user.domain.entity.User +import me.proton.core.user.domain.entity.UserAddress + +internal fun userKeysApi( + context: CryptoContext, + user: User, + userAddress: UserAddress, +) { + // User extends KeyHolder. + user.useKeys(context) { + val message = "message" + val data = message.toByteArray() + + // Encrypt + Sign Detached. + val encryptedText = encryptText(message) + val signedText = signText(message) + + val decryptedText = decryptText(encryptedText) + verifyText(decryptedText, signedText) + + // Encrypt + Sign Embedded (more secure). + val encryptedSignedText = encryptAndSignText(message) + decryptAndVerifyText(encryptedSignedText) + + val encryptedSignedData = encryptAndSignData(data) + decryptAndVerifyData(encryptedSignedData) + } + + // UserAddress extends KeyHolder. + userAddress.useKeys(context) { + val message = "message" + val data = message.toByteArray() + + // Encrypt + Sign Detached. + val encryptedText = encryptText(message) + val signedText = signText(message) + + decryptTextOrNull(encryptedText)?.let { + verifyText(it, signedText) + } + + // Encrypt + Sign Embedded (more secure). + val encryptedSignedText = encryptAndSignText(message) + decryptAndVerifyTextOrNull(encryptedSignedText) + + val encryptedSignedData = encryptAndSignData(data) + decryptAndVerifyDataOrNull(encryptedSignedData) + } +} diff --git a/user/src/main/AndroidManifest.xml b/user/src/main/AndroidManifest.xml new file mode 100644 index 000000000..205e48e1f --- /dev/null +++ b/user/src/main/AndroidManifest.xml @@ -0,0 +1,19 @@ + + +