diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt index 93067d8ebb..7885a05e9b 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/auth/data/WellKnown.kt @@ -53,5 +53,15 @@ data class WellKnown( val identityServer: WellKnownBaseConfig? = null, @Json(name = "m.integrations") - val integrations: JsonDict? = null + val integrations: JsonDict? = null, + + @Json(name = "im.vector.riot.e2ee") + val e2eAdminSetting: E2EWellKnownConfig? = null + +) + +@JsonClass(generateAdapter = true) +data class E2EWellKnownConfig( + @Json(name = "default") + val e2eDefault: Boolean = true ) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt index 1c2b8de83b..148d75282e 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/homeserver/HomeServerCapabilities.kt @@ -32,7 +32,12 @@ data class HomeServerCapabilities( /** * Default identity server url, provided in Wellknown */ - val defaultIdentityServerUrl: String? = null + val defaultIdentityServerUrl: String? = null, + /** + * Option to allow homeserver admins to set the default E2EE behaviour back to disabled for DMs / private rooms + * (as it was before) for various environments where this is desired. + */ + val adminE2EByDefault: Boolean = true ) { companion object { const val MAX_UPLOAD_FILE_SIZE_UNKNOWN = -1L diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt index 5204eef878..142d0d119f 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/RealmSessionStoreMigration.kt @@ -16,6 +16,7 @@ package im.vector.matrix.android.internal.database +import im.vector.matrix.android.internal.database.model.HomeServerCapabilitiesEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import io.realm.DynamicRealm import io.realm.RealmMigration @@ -28,6 +29,7 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { Timber.v("Migrating Realm Session from $oldVersion to $newVersion") if (oldVersion <= 0) migrateTo1(realm) + if (oldVersion <= 1) migrateTo2(realm) } private fun migrateTo1(realm: DynamicRealm) { @@ -40,4 +42,13 @@ class RealmSessionStoreMigration @Inject constructor() : RealmMigration { obj.setBoolean(RoomSummaryEntityFields.HAS_FAILED_SENDING, false) } } + + private fun migrateTo2(realm: DynamicRealm) { + Timber.d("Step 1 -> 2") + realm.schema.get("HomeServerCapabilitiesEntity") + ?.addField(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, Boolean::class.java) + ?.transform { obj -> + obj.setBoolean(HomeServerCapabilitiesEntityFields.ADMIN_E2_E_BY_DEFAULT, true) + } + } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt index f11f001e1c..416e169022 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/SessionRealmConfigurationFactory.kt @@ -46,7 +46,7 @@ internal class SessionRealmConfigurationFactory @Inject constructor( context: Context) { companion object { - const val SESSION_STORE_SCHEMA_VERSION = 1L + const val SESSION_STORE_SCHEMA_VERSION = 2L } private val sharedPreferences = context.getSharedPreferences("im.vector.matrix.android.realm", Context.MODE_PRIVATE) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt index a66f587cec..82a5ba79d1 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/HomeServerCapabilitiesMapper.kt @@ -29,7 +29,8 @@ internal object HomeServerCapabilitiesMapper { canChangePassword = entity.canChangePassword, maxUploadFileSize = entity.maxUploadFileSize, lastVersionIdentityServerSupported = entity.lastVersionIdentityServerSupported, - defaultIdentityServerUrl = entity.defaultIdentityServerUrl + defaultIdentityServerUrl = entity.defaultIdentityServerUrl, + adminE2EByDefault = entity.adminE2EByDefault ) } } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt index a6b250b8fa..9dd150c6f0 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/model/HomeServerCapabilitiesEntity.kt @@ -24,6 +24,7 @@ internal open class HomeServerCapabilitiesEntity( var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var lastVersionIdentityServerSupported: Boolean = false, var defaultIdentityServerUrl: String? = null, + var adminE2EByDefault: Boolean = true, var lastUpdatedTimestamp: Long = 0L ) : RealmObject() { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt index b26bbe7c5c..865b2513a8 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/homeserver/DefaultGetHomeServerCapabilitiesTask.kt @@ -108,13 +108,15 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor( if (getWellknownResult != null && getWellknownResult is WellknownResult.Prompt) { homeServerCapabilitiesEntity.defaultIdentityServerUrl = getWellknownResult.identityServerUrl - + homeServerCapabilitiesEntity.adminE2EByDefault = getWellknownResult.wellKnown.e2eAdminSetting?.e2eDefault ?: true // We are also checking for integration manager configurations val config = configExtractor.extract(getWellknownResult.wellKnown) if (config != null) { Timber.v("Extracted integration config : $config") realm.insertOrUpdate(config) } + } else { + homeServerCapabilitiesEntity.adminE2EByDefault = true } homeServerCapabilitiesEntity.lastUpdatedTimestamp = Date().time } diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt index 319671b230..b93993bc53 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewModel.kt @@ -38,6 +38,14 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted fun create(initialState: CreateDirectRoomViewState): CreateDirectRoomViewModel } + init { + setState { + copy( + hsAdminHasDisabledE2E = !session.getHomeServerCapabilities().adminE2EByDefault + ) + } + } + companion object : MvRxViewModelFactory { @JvmStatic @@ -63,7 +71,7 @@ class CreateDirectRoomViewModel @AssistedInject constructor(@Assisted }.exhaustive } setDirectMessage() - enableEncryptionIfInvitedUsersSupportIt = true + enableEncryptionIfInvitedUsersSupportIt = session.getHomeServerCapabilities().adminE2EByDefault } session.rx() diff --git a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt index 8bb8c3ce58..ff2253cd66 100644 --- a/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/createdirect/CreateDirectRoomViewState.kt @@ -21,5 +21,6 @@ import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized data class CreateDirectRoomViewState( - val createAndInviteState: Async = Uninitialized + val createAndInviteState: Async = Uninitialized, + val hsAdminHasDisabledE2E: Boolean = false ) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt index f9cb6ec3dc..926cef0668 100644 --- a/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt +++ b/vector/src/main/java/im/vector/riotx/features/media/ImageContentRenderer.kt @@ -209,7 +209,7 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder: Mode.THUMBNAIL -> contentUrlResolver.resolveThumbnail(data.url, size.width, size.height, ContentUrlResolver.ThumbnailMethod.SCALE) } // Fallback to base url - ?: data.url + ?: data.url.takeIf { it?.startsWith("content://") == true } GlideApp .with(imageView) diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt index 92e178c628..13649c113f 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomController.kt @@ -103,7 +103,10 @@ class CreateRoomController @Inject constructor(private val stringProvider: Strin id("encryption") enabled(enableFormElement) title(stringProvider.getString(R.string.create_room_encryption_title)) - summary(stringProvider.getString(R.string.create_room_encryption_description)) + summary( + if (viewState.hsAdminHasDisabledE2E) stringProvider.getString(R.string.settings_hs_admin_e2e_disabled) + else stringProvider.getString(R.string.create_room_encryption_description) + ) switchChecked(viewState.isEncrypted) listener { value -> diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt index b75e9444fe..e2d51dff83 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewModel.kt @@ -43,6 +43,15 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr fun create(initialState: CreateRoomViewState): CreateRoomViewModel } + init { + setState { + copy( + isEncrypted = !this.isPublic && session.getHomeServerCapabilities().adminE2EByDefault, + hsAdminHasDisabledE2E = !session.getHomeServerCapabilities().adminE2EByDefault + ) + } + } + companion object : MvRxViewModelFactory { @JvmStatic @@ -69,7 +78,12 @@ class CreateRoomViewModel @AssistedInject constructor(@Assisted initialState: Cr private fun setName(action: CreateRoomAction.SetName) = setState { copy(roomName = action.name) } - private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState { copy(isPublic = action.isPublic) } + private fun setIsPublic(action: CreateRoomAction.SetIsPublic) = setState { + copy( + isPublic = action.isPublic, + isEncrypted = !action.isPublic && session.getHomeServerCapabilities().adminE2EByDefault + ) + } private fun setIsInRoomDirectory(action: CreateRoomAction.SetIsInRoomDirectory) = setState { copy(isInRoomDirectory = action.isInRoomDirectory) } diff --git a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt index 810319d54f..1758f77818 100644 --- a/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt +++ b/vector/src/main/java/im/vector/riotx/features/roomdirectory/createroom/CreateRoomViewState.kt @@ -25,5 +25,6 @@ data class CreateRoomViewState( val isPublic: Boolean = false, val isInRoomDirectory: Boolean = false, val isEncrypted: Boolean = false, + val hsAdminHasDisabledE2E: Boolean = false, val asyncCreateRoomRequest: Async = Uninitialized ) : MvRxState diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt index c9284cf132..45558abe69 100755 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorPreferences.kt @@ -72,6 +72,8 @@ class VectorPreferences @Inject constructor(private val context: Context) { const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY" const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" + + const val SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT = "SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT" // const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY" // user diff --git a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt index 9d71c1712e..b60c2cb3a7 100644 --- a/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/settings/VectorSettingsSecurityPrivacyFragment.kt @@ -20,6 +20,9 @@ package im.vector.riotx.features.settings import android.annotation.SuppressLint import android.app.Activity import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.ViewGroup import android.widget.Button import android.widget.TextView import androidx.appcompat.app.AlertDialog @@ -28,6 +31,7 @@ import androidx.core.view.isVisible import androidx.preference.Preference import androidx.preference.PreferenceCategory import androidx.preference.SwitchPreference +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.textfield.TextInputEditText import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.internal.crypto.crosssigning.isVerified @@ -55,6 +59,7 @@ import im.vector.riotx.features.crypto.recover.BootstrapBottomSheet import im.vector.riotx.features.themes.ThemeUtils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.disposables.Disposable +import me.gujun.android.span.span import javax.inject.Inject class VectorSettingsSecurityPrivacyFragment @Inject constructor( @@ -96,6 +101,15 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( findPreference(VectorPreferences.SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY)!! } + override fun onCreateRecyclerView(inflater: LayoutInflater?, parent: ViewGroup?, savedInstanceState: Bundle?): RecyclerView { + return super.onCreateRecyclerView(inflater, parent, savedInstanceState).also { + // Insert animation are really annoying the first time the list is shown + // due to the way preference fragment is done, it's not trivial to disable it for first appearance only.. + // And it's not that an issue that this list is not animated, it's pretty static + it.itemAnimator = null + } + } + override fun onResume() { super.onResume() // My device name may have been updated @@ -109,6 +123,9 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( }.also { disposables.add(it) } + + val e2eByDefault = session.getHomeServerCapabilities().adminE2EByDefault + findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.isVisible = !e2eByDefault } private val secureBackupCategory by lazy { @@ -220,6 +237,19 @@ class VectorSettingsSecurityPrivacyFragment @Inject constructor( ThemeUtils.tintDrawable(it, ContextCompat.getDrawable(it, R.drawable.ic_secure_backup)!!, R.attr.vctr_settings_icon_tint_color) } + + findPreference(VectorPreferences.SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT)?.let { + it.icon = ThemeUtils.tintDrawableWithColor( + ContextCompat.getDrawable(requireContext(), R.drawable.ic_notification_privacy_warning)!!, + ContextCompat.getColor(requireContext(), R.color.riotx_destructive_accent) + ) + it.summary = span { + text = getString(R.string.settings_hs_admin_e2e_disabled) + textColor = ContextCompat.getColor(requireContext(), R.color.riotx_destructive_accent) + } + + it.isVisible = session.getHomeServerCapabilities().adminE2EByDefault + } } // Todo this should be refactored and use same state as 4S section diff --git a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt index 671c0b0ee1..603b6e2b29 100644 --- a/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/userdirectory/KnownUsersFragment.kt @@ -22,6 +22,7 @@ import android.view.MenuItem import android.view.View import android.widget.ScrollView import androidx.core.view.forEach +import androidx.core.view.isVisible import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.args import com.airbnb.mvrx.withState @@ -35,6 +36,8 @@ import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.setupAsSearch import im.vector.riotx.core.platform.VectorBaseFragment import im.vector.riotx.core.utils.DimensionConverter +import im.vector.riotx.features.createdirect.CreateDirectRoomViewModel +import im.vector.riotx.features.createdirect.CreateDirectRoomViewState import kotlinx.android.synthetic.main.fragment_known_users.* import javax.inject.Inject @@ -51,6 +54,8 @@ class KnownUsersFragment @Inject constructor( override fun getMenuRes() = args.menuResId private val viewModel: UserDirectoryViewModel by activityViewModel() + private val createDMViewModel: CreateDirectRoomViewModel by activityViewModel() + private lateinit var sharedActionViewModel: UserDirectorySharedActionViewModel override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -58,13 +63,17 @@ class KnownUsersFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(UserDirectorySharedActionViewModel::class.java) knownUsersTitle.text = args.title - vectorBaseActivity.setSupportActionBar(knownUsersToolbar) setupRecyclerView() setupFilterView() setupAddByMatrixIdView() setupAddFromPhoneBookView() setupCloseView() + + createDMViewModel.selectSubscribe(this, CreateDirectRoomViewState::hsAdminHasDisabledE2E) { + knownUsersE2EbyDefaultDisabled.isVisible = it + } + viewModel.selectSubscribe(this, UserDirectoryViewState::pendingInvitees) { renderSelectedUsers(it) } diff --git a/vector/src/main/res/layout/fragment_known_users.xml b/vector/src/main/res/layout/fragment_known_users.xml index 82ddea5323..2b8ee68418 100644 --- a/vector/src/main/res/layout/fragment_known_users.xml +++ b/vector/src/main/res/layout/fragment_known_users.xml @@ -106,6 +106,21 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/knownUsersFilter" /> + + + + + app:layout_constraintTop_toBottomOf="@id/knownUsersE2EbyDefaultDisabled" /> Cross-Signing is enabled.\nKeys are not trusted Cross-Signing is not enabled - + Your server admin has disabled end-to-end encryption by default in private rooms & Direct Messages. Active Sessions Show All Sessions Manage Sessions diff --git a/vector/src/main/res/xml/vector_settings_security_privacy.xml b/vector/src/main/res/xml/vector_settings_security_privacy.xml index 9bfe5e944b..5f249fb358 100644 --- a/vector/src/main/res/xml/vector_settings_security_privacy.xml +++ b/vector/src/main/res/xml/vector_settings_security_privacy.xml @@ -3,6 +3,16 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools"> + + + + - +