Add userConsent UI to the Discovery screen
This commit is contained in:
parent
ccf5d759a4
commit
d1e2d06538
|
@ -92,9 +92,24 @@ interface IdentityService {
|
|||
|
||||
/**
|
||||
* Search MatrixId of users providing email and phone numbers
|
||||
* Note the the user consent has to be set to true, or it will throw a UserConsentNotProvided failure
|
||||
* Application has to explicitly ask for the user consent.
|
||||
* Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
|
||||
*/
|
||||
fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable
|
||||
|
||||
/**
|
||||
* Return the current user consent
|
||||
*/
|
||||
fun getUserConsent(): Boolean
|
||||
|
||||
/**
|
||||
* Set the user consent. Application may have explicitly ask for the user consent to send their private data
|
||||
* (email and phone numbers) to the identity server.
|
||||
* Please see https://support.google.com/googleplay/android-developer/answer/9888076?hl=en for more details.
|
||||
*/
|
||||
fun setUserConsent(newValue: Boolean)
|
||||
|
||||
/**
|
||||
* Get the status of the current user's threePid
|
||||
* A lookup will be performed, but also pending binding state will be restored
|
||||
|
|
|
@ -24,6 +24,7 @@ sealed class IdentityServiceError : Failure.FeatureFailure() {
|
|||
object NoIdentityServerConfigured : IdentityServiceError()
|
||||
object TermsNotSignedException : IdentityServiceError()
|
||||
object BulkLookupSha256NotSupported : IdentityServiceError()
|
||||
object UserConsentNotProvided : IdentityServiceError()
|
||||
object BindingError : IdentityServiceError()
|
||||
object NoCurrentBindingError : IdentityServiceError()
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
|
|||
import org.matrix.android.sdk.internal.util.ensureProtocol
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.matrix.android.sdk.api.extensions.orFalse
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
@ -243,7 +244,20 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
))
|
||||
}
|
||||
|
||||
override fun getUserConsent(): Boolean {
|
||||
return identityStore.getIdentityData()?.userConsent.orFalse()
|
||||
}
|
||||
|
||||
override fun setUserConsent(newValue: Boolean) {
|
||||
identityStore.setUserConsent(newValue)
|
||||
}
|
||||
|
||||
override fun lookUp(threePids: List<ThreePid>, callback: MatrixCallback<List<FoundThreePid>>): Cancelable {
|
||||
if (!getUserConsent()) {
|
||||
callback.onFailure(IdentityServiceError.UserConsentNotProvided)
|
||||
return NoOpCancellable
|
||||
}
|
||||
|
||||
if (threePids.isEmpty()) {
|
||||
callback.onSuccess(emptyList())
|
||||
return NoOpCancellable
|
||||
|
@ -255,6 +269,9 @@ internal class DefaultIdentityService @Inject constructor(
|
|||
}
|
||||
|
||||
override fun getShareStatus(threePids: List<ThreePid>, callback: MatrixCallback<Map<ThreePid, SharedState>>): Cancelable {
|
||||
// Note: we do not require user consent here, because it is used for email and phone numbers that the user has already sent
|
||||
// to the home server. Identity server is another service though...
|
||||
|
||||
if (threePids.isEmpty()) {
|
||||
callback.onSuccess(emptyMap())
|
||||
return NoOpCancellable
|
||||
|
|
|
@ -52,6 +52,13 @@ internal fun IdentityDataEntity.Companion.setToken(realm: Realm,
|
|||
}
|
||||
}
|
||||
|
||||
internal fun IdentityDataEntity.Companion.setUserConsent(realm: Realm,
|
||||
newConsent: Boolean) {
|
||||
get(realm)?.apply {
|
||||
userConsent = newConsent
|
||||
}
|
||||
}
|
||||
|
||||
internal fun IdentityDataEntity.Companion.setHashDetails(realm: Realm,
|
||||
pepper: String,
|
||||
algorithms: List<String>) {
|
||||
|
|
|
@ -136,6 +136,7 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
IdentityServiceError.BulkLookupSha256NotSupported -> R.string.identity_server_error_bulk_sha256_not_supported
|
||||
IdentityServiceError.BindingError -> R.string.identity_server_error_binding_error
|
||||
IdentityServiceError.NoCurrentBindingError -> R.string.identity_server_error_no_current_binding_error
|
||||
IdentityServiceError.UserConsentNotProvided -> R.string.identity_server_user_consent_not_provided
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ sealed class DiscoverySettingsAction : VectorViewModelAction {
|
|||
|
||||
object DisconnectIdentityServer : DiscoverySettingsAction()
|
||||
data class ChangeIdentityServer(val url: String) : DiscoverySettingsAction()
|
||||
data class UpdateUserConsent(val newConsent: Boolean) : DiscoverySettingsAction()
|
||||
data class RevokeThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||
data class ShareThreePid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||
data class FinalizeBind3pid(val threePid: ThreePid) : DiscoverySettingsAction()
|
||||
|
|
|
@ -65,6 +65,7 @@ class DiscoverySettingsController @Inject constructor(
|
|||
buildIdentityServerSection(data)
|
||||
val hasIdentityServer = data.identityServer().isNullOrBlank().not()
|
||||
if (hasIdentityServer && !data.termsNotSigned) {
|
||||
buildConsentSection(data)
|
||||
buildEmailsSection(data.emailList)
|
||||
buildMsisdnSection(data.phoneNumbersList)
|
||||
}
|
||||
|
@ -72,6 +73,38 @@ class DiscoverySettingsController @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildConsentSection(data: DiscoverySettingsState) {
|
||||
settingsSectionTitleItem {
|
||||
id("idConsentTitle")
|
||||
titleResId(R.string.settings_discovery_consent_title)
|
||||
}
|
||||
|
||||
if (data.userConsent) {
|
||||
settingsInfoItem {
|
||||
id("idConsentInfo")
|
||||
helperTextResId(R.string.settings_discovery_consent_notice_on)
|
||||
}
|
||||
settingsButtonItem {
|
||||
id("idConsentButton")
|
||||
colorProvider(colorProvider)
|
||||
buttonTitleId(R.string.settings_discovery_consent_action_revoke)
|
||||
buttonStyle(ButtonStyle.DESTRUCTIVE)
|
||||
buttonClickListener { listener?.onTapUpdateUserConsent(false) }
|
||||
}
|
||||
} else {
|
||||
settingsInfoItem {
|
||||
id("idConsentInfo")
|
||||
helperTextResId(R.string.settings_discovery_consent_notice_off)
|
||||
}
|
||||
settingsButtonItem {
|
||||
id("idConsentButton")
|
||||
colorProvider(colorProvider)
|
||||
buttonTitleId(R.string.settings_discovery_consent_action_give_consent)
|
||||
buttonClickListener { listener?.onTapUpdateUserConsent(true) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildIdentityServerSection(data: DiscoverySettingsState) {
|
||||
val identityServer = data.identityServer() ?: stringProvider.getString(R.string.none)
|
||||
|
||||
|
@ -359,6 +392,7 @@ class DiscoverySettingsController @Inject constructor(
|
|||
fun sendMsisdnVerificationCode(threePid: ThreePid.Msisdn, code: String)
|
||||
fun onTapChangeIdentityServer()
|
||||
fun onTapDisconnectIdentityServer()
|
||||
fun onTapUpdateUserConsent(newValue: Boolean)
|
||||
fun onTapRetryToRetrieveBindings()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,6 +170,23 @@ class DiscoverySettingsFragment @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onTapUpdateUserConsent(newValue: Boolean) {
|
||||
if (newValue) {
|
||||
withState(viewModel) { state ->
|
||||
AlertDialog.Builder(requireActivity())
|
||||
.setTitle(R.string.identity_server_consent_dialog_title)
|
||||
.setMessage(getString(R.string.identity_server_consent_dialog_content, state.identityServer.invoke()))
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(true))
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
} else {
|
||||
viewModel.handle(DiscoverySettingsAction.UpdateUserConsent(false))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTapRetryToRetrieveBindings() {
|
||||
viewModel.handle(DiscoverySettingsAction.RetrieveBinding)
|
||||
}
|
||||
|
|
|
@ -25,5 +25,6 @@ data class DiscoverySettingsState(
|
|||
val emailList: Async<List<PidInfo>> = Uninitialized,
|
||||
val phoneNumbersList: Async<List<PidInfo>> = Uninitialized,
|
||||
// Can be true if terms are updated
|
||||
val termsNotSigned: Boolean = false
|
||||
val termsNotSigned: Boolean = false,
|
||||
val userConsent: Boolean = false
|
||||
) : MvRxState
|
||||
|
|
|
@ -63,7 +63,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
val identityServerUrl = identityService.getCurrentIdentityServerUrl()
|
||||
val currentIS = state.identityServer()
|
||||
setState {
|
||||
copy(identityServer = Success(identityServerUrl))
|
||||
copy(
|
||||
identityServer = Success(identityServerUrl),
|
||||
userConsent = false
|
||||
)
|
||||
}
|
||||
if (currentIS != identityServerUrl) retrieveBinding()
|
||||
}
|
||||
|
@ -71,7 +74,10 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
|
||||
init {
|
||||
setState {
|
||||
copy(identityServer = Success(identityService.getCurrentIdentityServerUrl()))
|
||||
copy(
|
||||
identityServer = Success(identityService.getCurrentIdentityServerUrl()),
|
||||
userConsent = identityService.getUserConsent()
|
||||
)
|
||||
}
|
||||
startListenToIdentityManager()
|
||||
observeThreePids()
|
||||
|
@ -97,6 +103,7 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
DiscoverySettingsAction.RetrieveBinding -> retrieveBinding()
|
||||
DiscoverySettingsAction.DisconnectIdentityServer -> disconnectIdentityServer()
|
||||
is DiscoverySettingsAction.ChangeIdentityServer -> changeIdentityServer(action)
|
||||
is DiscoverySettingsAction.UpdateUserConsent -> handleUpdateUserConsent(action)
|
||||
is DiscoverySettingsAction.RevokeThreePid -> revokeThreePid(action)
|
||||
is DiscoverySettingsAction.ShareThreePid -> shareThreePid(action)
|
||||
is DiscoverySettingsAction.FinalizeBind3pid -> finalizeBind3pid(action, true)
|
||||
|
@ -105,13 +112,23 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleUpdateUserConsent(action: DiscoverySettingsAction.UpdateUserConsent) {
|
||||
identityService.setUserConsent(action.newConsent)
|
||||
setState { copy(userConsent = action.newConsent) }
|
||||
}
|
||||
|
||||
private fun disconnectIdentityServer() {
|
||||
setState { copy(identityServer = Loading()) }
|
||||
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
awaitCallback<Unit> { session.identityService().disconnect(it) }
|
||||
setState { copy(identityServer = Success(null)) }
|
||||
setState {
|
||||
copy(
|
||||
identityServer = Success(null),
|
||||
userConsent = false
|
||||
)
|
||||
}
|
||||
} catch (failure: Throwable) {
|
||||
setState { copy(identityServer = Fail(failure)) }
|
||||
}
|
||||
|
@ -126,7 +143,12 @@ class DiscoverySettingsViewModel @AssistedInject constructor(
|
|||
val data = awaitCallback<String?> {
|
||||
session.identityService().setNewIdentityServer(action.url, it)
|
||||
}
|
||||
setState { copy(identityServer = Success(data)) }
|
||||
setState {
|
||||
copy(
|
||||
identityServer = Success(data),
|
||||
userConsent = false
|
||||
)
|
||||
}
|
||||
retrieveBinding()
|
||||
} catch (failure: Throwable) {
|
||||
setState { copy(identityServer = Fail(failure)) }
|
||||
|
|
|
@ -1793,6 +1793,14 @@
|
|||
<string name="settings_discovery_confirm_mail">We sent you a confirm email to %s, check your email and click on the confirmation link</string>
|
||||
<string name="settings_discovery_confirm_mail_not_clicked">We sent you a confirm email to %s, please first check your email and click on the confirmation link</string>
|
||||
<string name="settings_discovery_mail_pending">Pending</string>
|
||||
<string name="settings_discovery_consent_title">Send emails and phone numbers</string>
|
||||
<string name="settings_discovery_consent_notice_on">You have given your consent to send emails and phone numbers to this identity server to discover other users from your contacts.</string>
|
||||
<string name="settings_discovery_consent_notice_off">You have not given your consent to send emails and phone numbers to this identity server to discover other users from your contacts.</string>
|
||||
<string name="settings_discovery_consent_action_revoke">Revoke my consent</string>
|
||||
<string name="settings_discovery_consent_action_give_consent">Give consent</string>
|
||||
|
||||
<string name="identity_server_consent_dialog_title">Send emails and phone numbers</string>
|
||||
<string name="identity_server_consent_dialog_content">In order to discover existing contacts you know, do you accept to send your contact data (phone numbers and/or emails) to the configured Identity Server (%1$s)?\n\nFor more privacy, the sent data will be hashed before being sent.</string>
|
||||
|
||||
<string name="settings_discovery_enter_identity_server">Enter an identity server URL</string>
|
||||
<string name="settings_discovery_bad_identity_server">Could not connect to identity server</string>
|
||||
|
@ -2527,6 +2535,7 @@
|
|||
<string name="identity_server_error_bulk_sha256_not_supported">For your privacy, Element only supports sending hashed user emails and phone number.</string>
|
||||
<string name="identity_server_error_binding_error">The association has failed.</string>
|
||||
<string name="identity_server_error_no_current_binding_error">The is no current association with this identifier.</string>
|
||||
<string name="identity_server_user_consent_not_provided">The user consent has not been provided.</string>
|
||||
|
||||
<string name="identity_server_set_default_notice">Your homeserver (%1$s) proposes to use %2$s for your identity server</string>
|
||||
<string name="identity_server_set_default_submit">Use %1$s</string>
|
||||
|
|
Loading…
Reference in New Issue