feat: add authentication for pCloud (including UI)

This commit is contained in:
Manuel Jenny 2021-03-16 14:55:05 +01:00
parent 6a249056b0
commit 550415627e
No known key found for this signature in database
GPG Key ID: 1C80FE62B2BEAA18
12 changed files with 178 additions and 5 deletions

View File

@ -0,0 +1,23 @@
package org.cryptomator.domain.usecases.cloud;
import org.cryptomator.domain.PCloudCloud;
import org.cryptomator.domain.exception.BackendException;
import org.cryptomator.domain.repository.CloudContentRepository;
import org.cryptomator.generator.Parameter;
import org.cryptomator.generator.UseCase;
@UseCase
class ConnectToPCloud {
private final CloudContentRepository cloudContentRepository;
private final PCloudCloud cloud;
public ConnectToPCloud(CloudContentRepository cloudContentRepository, @Parameter PCloudCloud cloud) {
this.cloudContentRepository = cloudContentRepository;
this.cloud = cloud;
}
public void execute() throws BackendException {
cloudContentRepository.checkAuthenticationAndRetrieveCurrentAccount(cloud);
}
}

View File

@ -50,7 +50,11 @@ android {
useProguard false
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY') + "\""
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY')]
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID') + "\""
manifestPlaceholders = [
DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY'),
PCLOUD_CLIENT_ID: getApiKey('PCLOUD_CLIENT_ID')
]
resValue "string", "app_id", androidApplicationId
}
@ -64,7 +68,11 @@ android {
testCoverageEnabled false
buildConfigField "String", "DROPBOX_API_KEY", "\"" + getApiKey('DROPBOX_API_KEY_DEBUG') + "\""
manifestPlaceholders = [DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG')]
buildConfigField "String", "PCLOUD_CLIENT_ID", "\"" + getApiKey('PCLOUD_CLIENT_ID_DEBUG') + "\""
manifestPlaceholders = [
DROPBOX_API_KEY: getApiKey('DROPBOX_API_KEY_DEBUG'),
PCLOUD_CLIENT_ID: getApiKey('PCLOUD_CLIENT_ID_DEBUG')
]
applicationIdSuffix ".debug"
versionNameSuffix '-DEBUG'

View File

@ -4,14 +4,21 @@ import android.content.ActivityNotFoundException
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.util.Log
import android.widget.Toast
import androidx.annotation.RequiresApi
import com.pcloud.sdk.AuthorizationActivity
import com.pcloud.sdk.AuthorizationData
import com.pcloud.sdk.AuthorizationRequest
import com.pcloud.sdk.AuthorizationResult
import org.cryptomator.domain.Cloud
import org.cryptomator.domain.LocalStorageCloud
import org.cryptomator.domain.PCloudCloud
import org.cryptomator.domain.Vault
import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
import org.cryptomator.domain.usecases.cloud.RemoveCloudUseCase
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
@ -26,6 +33,7 @@ import org.cryptomator.presentation.model.WebDavCloudModel
import org.cryptomator.presentation.model.mappers.CloudModelMapper
import org.cryptomator.presentation.ui.activity.view.CloudConnectionListView
import org.cryptomator.presentation.workflow.ActivityResult
import org.cryptomator.util.crypto.CredentialCryptor
import java.util.*
import java.util.concurrent.atomic.AtomicReference
import javax.inject.Inject
@ -34,6 +42,7 @@ import timber.log.Timber
@PerView
class CloudConnectionListPresenter @Inject constructor( //
private val getCloudsUseCase: GetCloudsUseCase, //
private val getUsernameUseCase: GetUsernameUseCase, //
private val removeCloudUseCase: RemoveCloudUseCase, //
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
private val getVaultListUseCase: GetVaultListUseCase, //
@ -122,6 +131,18 @@ class CloudConnectionListPresenter @Inject constructor( //
when (selectedCloudType.get()) {
CloudTypeModel.WEBDAV -> requestActivityResult(ActivityResultCallbacks.addChangeWebDavCloud(), //
Intents.webDavAddOrChangeIntent())
CloudTypeModel.PCLOUD -> {
val authIntent: Intent = AuthorizationActivity.createIntent(
this.context(),
AuthorizationRequest.create()
.setType(AuthorizationRequest.Type.TOKEN)
.setClientId("tsAamgqqwk7")
.setForceAccessApproval(true)
.addPermission("manageshares")
.build())
requestActivityResult(ActivityResultCallbacks.pCloudAuthenticationFinished(), //
authIntent)
}
CloudTypeModel.LOCAL -> openDocumentTree()
}
}
@ -162,6 +183,77 @@ class CloudConnectionListPresenter @Inject constructor( //
loadCloudList()
}
@Callback
fun pCloudAuthenticationFinished(activityResult: ActivityResult?) {
val authData: AuthorizationData = AuthorizationActivity.getResult(activityResult!!.intent())
val result: AuthorizationResult = authData.result
when (result) {
AuthorizationResult.ACCESS_GRANTED -> {
val accessToken: String = CredentialCryptor //
.getInstance(this.context()) //
.encrypt(authData.token)
val pCloudSkeleton: PCloudCloud = PCloudCloud.aPCloudCloud() //
.withAccessToken(accessToken)
.withUrl(authData.apiHost)
.build();
getUsernameUseCase //
.withCloud(pCloudSkeleton) //
.run(object : DefaultResultHandler<String>() {
override fun onSuccess(username: String?) {
prepareForSavingPCloudCloud(PCloudCloud.aCopyOf(pCloudSkeleton).withUsername(username).build())
}
})
Log.d("pCloud", "Account access granted, authData:\n$authData")
}
AuthorizationResult.ACCESS_DENIED -> //TODO: Add proper handling for denied grants.
Log.d("pCloud", "Account access denied")
AuthorizationResult.AUTH_ERROR -> {
//TODO: Add error handling.
Log.d("pCloud", """Account access grant error: ${authData.errorMessage}""".trimIndent())
}
AuthorizationResult.CANCELLED -> {
//TODO: Handle cancellation.
Log.d("pCloud", "Account access grant cancelled:")
}
}
}
fun prepareForSavingPCloudCloud(cloud: PCloudCloud) {
getCloudsUseCase //
.withCloudType(CloudTypeModel.valueOf(selectedCloudType.get())) //
.run(object : DefaultResultHandler<List<Cloud>>() {
override fun onSuccess(clouds: List<Cloud>) {
// here check if a cloud with the same mail adress already exists,
// if so update (in case of the token changed) else create a new one
val existingPCloudCloud: PCloudCloud? = clouds.firstOrNull {
(it as PCloudCloud).username() == cloud.username()
} as PCloudCloud?
if (existingPCloudCloud != null && existingPCloudCloud.accessToken() != cloud.accessToken()) {
saveCloud(PCloudCloud.aCopyOf(existingPCloudCloud) //
.withUrl(cloud.url())
.withAccessToken(cloud.accessToken())
.build())
} else if (existingPCloudCloud == null) {
saveCloud(cloud);
}
}
})
}
fun saveCloud(cloud: PCloudCloud) {
addOrChangeCloudConnectionUseCase //
.withCloud(cloud) //
.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
loadCloudList()
}
})
}
@Callback
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
fun pickedLocalStorageLocation(result: ActivityResult) {

View File

@ -2,6 +2,7 @@ package org.cryptomator.presentation.presenter
import org.cryptomator.domain.Cloud
import org.cryptomator.domain.LocalStorageCloud
import org.cryptomator.domain.PCloudCloud
import org.cryptomator.domain.WebDavCloud
import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.exception.FatalBackendException
@ -16,6 +17,7 @@ import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.CloudTypeModel
import org.cryptomator.presentation.model.LocalStorageModel
import org.cryptomator.presentation.model.PCloudCloudModel
import org.cryptomator.presentation.model.WebDavCloudModel
import org.cryptomator.presentation.model.mappers.CloudModelMapper
import org.cryptomator.presentation.ui.activity.view.CloudSettingsView
@ -34,6 +36,7 @@ class CloudSettingsPresenter @Inject constructor( //
private val nonSingleLoginClouds: Set<CloudTypeModel> = EnumSet.of( //
CloudTypeModel.CRYPTO, //
CloudTypeModel.LOCAL, //
CloudTypeModel.PCLOUD, //
CloudTypeModel.WEBDAV)
fun loadClouds() {
@ -41,7 +44,7 @@ class CloudSettingsPresenter @Inject constructor( //
}
fun onCloudClicked(cloudModel: CloudModel) {
if (isWebdavOrLocal(cloudModel)) {
if (isWebdavOrPCloudOrLocal(cloudModel)) {
startConnectionListActivity(cloudModel.cloudType())
} else {
if (isLoggedIn(cloudModel)) {
@ -58,8 +61,8 @@ class CloudSettingsPresenter @Inject constructor( //
}
}
private fun isWebdavOrLocal(cloudModel: CloudModel): Boolean {
return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel
private fun isWebdavOrPCloudOrLocal(cloudModel: CloudModel): Boolean {
return cloudModel is WebDavCloudModel || cloudModel is LocalStorageModel || cloudModel is PCloudCloudModel
}
private fun loginCloud(cloudModel: CloudModel) {
@ -91,6 +94,7 @@ class CloudSettingsPresenter @Inject constructor( //
private fun effectiveTitle(cloudTypeModel: CloudTypeModel): String {
when (cloudTypeModel) {
CloudTypeModel.WEBDAV -> return context().getString(R.string.screen_cloud_settings_webdav_connections)
CloudTypeModel.PCLOUD -> return context().getString(R.string.screen_cloud_settings_pcloud_connections)
CloudTypeModel.LOCAL -> return context().getString(R.string.screen_cloud_settings_local_storage_locations)
}
return context().getString(R.string.screen_cloud_settings_title)
@ -123,6 +127,7 @@ class CloudSettingsPresenter @Inject constructor( //
.toMutableList() //
.also {
it.add(aWebdavCloud())
it.add(aPCloudCloud())
it.add(aLocalCloud())
}
view?.render(cloudModel)
@ -132,6 +137,10 @@ class CloudSettingsPresenter @Inject constructor( //
return WebDavCloudModel(WebDavCloud.aWebDavCloudCloud().build())
}
private fun aPCloudCloud(): PCloudCloudModel {
return PCloudCloudModel(PCloudCloud.aPCloudCloud().build())
}
private fun aLocalCloud(): CloudModel {
return LocalStorageModel(LocalStorageCloud.aLocalStorage().build())
}

View File

@ -7,6 +7,7 @@ import org.cryptomator.domain.exception.FatalBackendException
import org.cryptomator.presentation.R
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.LocalStorageModel
import org.cryptomator.presentation.model.PCloudCloudModel
import org.cryptomator.presentation.model.WebDavCloudModel
import org.cryptomator.presentation.model.comparator.CloudModelComparator
import org.cryptomator.presentation.ui.adapter.CloudConnectionListAdapter.CloudConnectionHolder
@ -54,6 +55,8 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter<CloudModel, Clo
if (cloudModel is WebDavCloudModel) {
bindWebDavCloudModel(cloudModel)
} else if (cloudModel is PCloudCloudModel) {
bindPCloudCloudModel(cloudModel)
} else if (cloudModel is LocalStorageModel) {
bindLocalStorageCloudModel(cloudModel)
}
@ -70,6 +73,11 @@ internal constructor(context: Context) : RecyclerViewBaseAdapter<CloudModel, Clo
}
private fun bindPCloudCloudModel(cloudModel: PCloudCloudModel) {
itemView.cloudText.text = cloudModel.username()
itemView.cloudSubText.visibility = View.GONE
}
private fun bindLocalStorageCloudModel(cloudModel: LocalStorageModel) {
if (cloudModel.location().isEmpty()) {
itemView.cloudText.text = cloudModel.storage()

View File

@ -41,6 +41,8 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter<CloudModel,
if (webdav(cloudModel.cloudType())) {
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_webdav_connections)
} else if (pCloud(cloudModel.cloudType())) {
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_pcloud_connections)
} else if (local(cloudModel.cloudType())) {
itemView.cloudName.text = context.getString(R.string.screen_cloud_settings_local_storage_locations)
} else {
@ -79,4 +81,8 @@ constructor(private val context: Context) : RecyclerViewBaseAdapter<CloudModel,
private fun webdav(cloudType: CloudTypeModel): Boolean {
return CloudTypeModel.WEBDAV == cloudType
}
private fun pCloud(cloudType: CloudTypeModel): Boolean {
return CloudTypeModel.PCLOUD == cloudType
}
}

View File

@ -172,6 +172,7 @@
<string name="screen_settings_background_unlock_preparation_label">Vorbereitungen zum Entsperren im Hintergrund</string>
<!-- ## screen: cloud settings -->
<string name="screen_cloud_settings_webdav_connections">WebDAV-Verbindungen</string>
<string name="screen_cloud_settings_pcloud_connections">pCloud-Verbindungen</string>
<string name="screen_cloud_settings_local_storage_locations">Lokale Speicherorte</string>
<string name="screen_cloud_settings_log_in_to">Einloggen in</string>
<string name="screen_cloud_settings_sign_out_from_cloud">Abmelden von</string>

View File

@ -110,6 +110,7 @@
<string name="screen_settings_section_version">Versión</string>
<!-- ## screen: cloud settings -->
<string name="screen_cloud_settings_webdav_connections">Conexiones de WebDAV</string>
<string name="screen_cloud_settings_pcloud_connections">Conexiones de pCloud</string>
<string name="screen_cloud_settings_local_storage_locations">Ubicaciones de almacenamiento local</string>
<string name="screen_cloud_settings_log_in_to">Iniciar sesión en</string>
<string name="screen_cloud_settings_sign_out_from_cloud">Cerrar sesión de</string>

View File

@ -173,6 +173,7 @@
<string name="screen_settings_background_unlock_preparation_label">Préparations du déverrouillage en arrière-plan</string>
<!-- ## screen: cloud settings -->
<string name="screen_cloud_settings_webdav_connections">Connexions WebDAV</string>
<string name="screen_cloud_settings_pcloud_connections">Connexions pCloud</string>
<string name="screen_cloud_settings_local_storage_locations">Emplacements du stockage local</string>
<string name="screen_cloud_settings_log_in_to">Se connecter à</string>
<string name="screen_cloud_settings_sign_out_from_cloud">Se déconnecter de</string>

View File

@ -168,6 +168,7 @@
<string name="screen_settings_background_unlock_preparation_label">Arka planda kilit açma</string>
<!-- ## screen: cloud settings -->
<string name="screen_cloud_settings_webdav_connections">WebDAV bağlantıları</string>
<string name="screen_cloud_settings_pcloud_connections">pCloud bağlantıları</string>
<string name="screen_cloud_settings_local_storage_locations">Yerel depolama konumları</string>
<string name="screen_cloud_settings_log_in_to">Giriş</string>
<string name="screen_cloud_settings_sign_out_from_cloud">Oturumunu kapat</string>

View File

@ -254,6 +254,7 @@
<!-- ## screen: cloud settings -->
<string name="screen_cloud_settings_title">@string/screen_settings_cloud_settings_label</string>
<string name="screen_cloud_settings_webdav_connections">WebDAV connections</string>
<string name="screen_cloud_settings_pcloud_connections">pCloud connections</string>
<string name="screen_cloud_settings_local_storage_locations">Local storage locations</string>
<string name="screen_cloud_settings_log_in_to">Log in to</string>
<string name="screen_cloud_settings_sign_out_from_cloud">Sign out from</string>

View File

@ -34,6 +34,7 @@ import org.cryptomator.presentation.exception.PermissionNotGrantedException
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.CloudTypeModel
import org.cryptomator.presentation.model.PCloudCloudModel
import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.model.ProgressStateModel
import org.cryptomator.presentation.model.WebDavCloudModel
@ -65,6 +66,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
DropboxAuthStrategy(), //
GoogleDriveAuthStrategy(), //
OnedriveAuthStrategy(), //
PCloudAuthStrategy(), //
WebDAVAuthStrategy(), //
LocalStorageAuthStrategy() //
)
@ -282,6 +284,26 @@ class AuthenticateCloudPresenter @Inject constructor( //
}
}
private inner class PCloudAuthStrategy : AuthStrategy {
override fun supports(cloud: CloudModel): Boolean {
return cloud.cloudType() == CloudTypeModel.PCLOUD
}
override fun resumed(intent: AuthenticateCloudIntent) {
handlePCloudAuthenticationExceptionIfRequired(intent.cloud() as PCloudCloudModel, intent.error())
}
private fun handlePCloudAuthenticationExceptionIfRequired(cloud: PCloudCloudModel, e: AuthenticationException) {
Timber.tag("AuthicateCloudPrester").e(e)
when {
ExceptionUtil.contains(e, WrongCredentialsException::class.java) -> {
failAuthentication(cloud.name())
}
}
}
}
private inner class WebDAVAuthStrategy : AuthStrategy {
override fun supports(cloud: CloudModel): Boolean {