Merge branch 'develop' of https://github.com/vector-im/riotX-android into develop

Signed-off-by: Waylon Cude <waylon.cude@finzdani.net>
This commit is contained in:
Waylon Cude 2020-04-16 03:10:06 -07:00
commit d20cf484ff
No known key found for this signature in database
GPG Key ID: 4D6F82187581473F
32 changed files with 496 additions and 106 deletions

View File

@ -2,6 +2,7 @@ Changes in RiotX 0.19.0 (2020-XX-XX)
=================================================== ===================================================
Features ✨: Features ✨:
- Change password (#528)
- Cross-Signing | Support SSSS secret sharing (#944) - Cross-Signing | Support SSSS secret sharing (#944)
- Cross-Signing | Verify new session from existing session (#1134) - Cross-Signing | Verify new session from existing session (#1134)
- Cross-Signing | Bootstraping cross signing with 4S from mobile (#985) - Cross-Signing | Bootstraping cross signing with 4S from mobile (#985)
@ -31,6 +32,7 @@ Bugfix 🐛:
- Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210) - Cross-Signing | web <-> riotX After QR code scan, gossiping fails (#1210)
- Fix crash when trying to download file without internet connection (#1229) - Fix crash when trying to download file without internet connection (#1229)
- Local echo are not updated in timeline (for failed & encrypted states) - Local echo are not updated in timeline (for failed & encrypted states)
- Render image event even if thumbnail_info does not have mimetype defined (#1209)
- RiotX now uses as many threads as it needs to do work and send messages (#1221) - RiotX now uses as many threads as it needs to do work and send messages (#1221)
Translations 🗣: Translations 🗣:
@ -43,6 +45,7 @@ Build 🧱:
- Compile with Android SDK 29 (Android Q) - Compile with Android SDK 29 (Android Q)
Other changes: Other changes:
- Add a setting to prevent screenshots of the application, disabled by default (#1027)
- Increase File Logger capacities ( + use dev log preferences) - Increase File Logger capacities ( + use dev log preferences)
Changes in RiotX 0.18.1 (2020-03-17) Changes in RiotX 0.18.1 (2020-03-17)

View File

@ -31,3 +31,9 @@ fun Throwable.shouldBeRetried(): Boolean {
return this is Failure.NetworkConnection return this is Failure.NetworkConnection
|| (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED) || (this is Failure.ServerError && error.code == MatrixError.M_LIMIT_EXCEEDED)
} }
fun Throwable.isInvalidPassword(): Boolean {
return this is Failure.ServerError
&& error.code == MatrixError.M_FORBIDDEN
&& error.message == "Invalid password"
}

View File

@ -21,6 +21,7 @@ import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.failure.GlobalError import im.vector.matrix.android.api.failure.GlobalError
import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.account.AccountService
import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.cache.CacheService import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
@ -59,7 +60,8 @@ interface Session :
InitialSyncProgressService, InitialSyncProgressService,
HomeServerCapabilitiesService, HomeServerCapabilitiesService,
SecureStorageService, SecureStorageService,
AccountDataService { AccountDataService,
AccountService {
/** /**
* The params associated to the session * The params associated to the session

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.account
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
/**
* This interface defines methods to manage the account. It's implemented at the session level.
*/
interface AccountService {
/**
* Ask the homeserver to change the password.
* @param password Current password.
* @param newPassword New password
*/
fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.account.model
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
/**
* Class to pass request parameters to update the password.
*/
@JsonClass(generateAdapter = true)
internal data class ChangePasswordParams(
@Json(name = "auth")
val auth: UserPasswordAuth? = null,
@Json(name = "new_password")
val newPassword: String? = null
) {
companion object {
fun create(userId: String, oldPassword: String, newPassword: String): ChangePasswordParams {
return ChangePasswordParams(
auth = UserPasswordAuth(user = userId, password = oldPassword),
newPassword = newPassword
)
}
}
}

View File

@ -17,6 +17,10 @@
package im.vector.matrix.android.api.session.homeserver package im.vector.matrix.android.api.session.homeserver
data class HomeServerCapabilities( data class HomeServerCapabilities(
/**
* True if it is possible to change the password of the account.
*/
val canChangePassword: Boolean = true,
/** /**
* Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet * Max size of file which can be uploaded to the homeserver in bytes. [MAX_UPLOAD_FILE_SIZE_UNKNOWN] if unknown or not retrieved yet
*/ */

View File

@ -24,7 +24,7 @@ data class AudioInfo(
/** /**
* The mimetype of the audio e.g. "audio/aac". * The mimetype of the audio e.g. "audio/aac".
*/ */
@Json(name = "mimetype") val mimeType: String, @Json(name = "mimetype") val mimeType: String?,
/** /**
* The size of the audio clip in bytes. * The size of the audio clip in bytes.

View File

@ -39,5 +39,5 @@ data class ThumbnailInfo(
/** /**
* The mimetype of the image, e.g. "image/jpeg". * The mimetype of the image, e.g. "image/jpeg".
*/ */
@Json(name = "mimetype") val mimeType: String @Json(name = "mimetype") val mimeType: String?
) )

View File

@ -21,7 +21,7 @@ import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
/** /**
* This class provides the authentication data to delete a device * This class provides the authentication data by using user and password
*/ */
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class UserPasswordAuth( data class UserPasswordAuth(

View File

@ -177,7 +177,7 @@ internal class DefaultQrCodeVerificationTransaction(
}.exhaustive }.exhaustive
if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) { if (!canTrustOtherUserMasterKey && toVerifyDeviceIds.isEmpty()) {
// // Nothing to verify // Nothing to verify
cancel(CancelCode.MismatchedKeys) cancel(CancelCode.MismatchedKeys)
return return
} }

View File

@ -26,12 +26,14 @@ internal object HomeServerCapabilitiesMapper {
fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities { fun map(entity: HomeServerCapabilitiesEntity): HomeServerCapabilities {
return HomeServerCapabilities( return HomeServerCapabilities(
canChangePassword = entity.canChangePassword,
maxUploadFileSize = entity.maxUploadFileSize maxUploadFileSize = entity.maxUploadFileSize
) )
} }
fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity { fun map(domain: HomeServerCapabilities): HomeServerCapabilitiesEntity {
return HomeServerCapabilitiesEntity( return HomeServerCapabilitiesEntity(
canChangePassword = domain.canChangePassword,
maxUploadFileSize = domain.maxUploadFileSize maxUploadFileSize = domain.maxUploadFileSize
) )
} }

View File

@ -20,6 +20,7 @@ import im.vector.matrix.android.api.session.homeserver.HomeServerCapabilities
import io.realm.RealmObject import io.realm.RealmObject
internal open class HomeServerCapabilitiesEntity( internal open class HomeServerCapabilitiesEntity(
var canChangePassword: Boolean = true,
var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN, var maxUploadFileSize: Long = HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN,
var lastUpdatedTimestamp: Long = 0L var lastUpdatedTimestamp: Long = 0L
) : RealmObject() { ) : RealmObject() {

View File

@ -25,6 +25,7 @@ import im.vector.matrix.android.api.failure.GlobalError
import im.vector.matrix.android.api.pushrules.PushRuleService import im.vector.matrix.android.api.pushrules.PushRuleService
import im.vector.matrix.android.api.session.InitialSyncProgressService import im.vector.matrix.android.api.session.InitialSyncProgressService
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.account.AccountService
import im.vector.matrix.android.api.session.accountdata.AccountDataService import im.vector.matrix.android.api.session.accountdata.AccountDataService
import im.vector.matrix.android.api.session.cache.CacheService import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
@ -94,6 +95,7 @@ internal class DefaultSession @Inject constructor(
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>, private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>,
private val accountDataService: Lazy<AccountDataService>, private val accountDataService: Lazy<AccountDataService>,
private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>, private val _sharedSecretStorageService: Lazy<SharedSecretStorageService>,
private val accountService: Lazy<AccountService>,
private val timelineEventDecryptor: TimelineEventDecryptor, private val timelineEventDecryptor: TimelineEventDecryptor,
private val shieldTrustUpdater: ShieldTrustUpdater) private val shieldTrustUpdater: ShieldTrustUpdater)
: Session, : Session,
@ -110,7 +112,8 @@ internal class DefaultSession @Inject constructor(
SecureStorageService by secureStorageService.get(), SecureStorageService by secureStorageService.get(),
HomeServerCapabilitiesService by homeServerCapabilitiesService.get(), HomeServerCapabilitiesService by homeServerCapabilitiesService.get(),
ProfileService by profileService.get(), ProfileService by profileService.get(),
AccountDataService by accountDataService.get() { AccountDataService by accountDataService.get(),
AccountService by accountService.get() {
override val sharedSecretStorageService: SharedSecretStorageService override val sharedSecretStorageService: SharedSecretStorageService
get() = _sharedSecretStorageService.get() get() = _sharedSecretStorageService.get()

View File

@ -28,6 +28,7 @@ import im.vector.matrix.android.internal.crypto.verification.SendVerificationMes
import im.vector.matrix.android.internal.di.MatrixComponent import im.vector.matrix.android.internal.di.MatrixComponent
import im.vector.matrix.android.internal.di.SessionAssistedInjectModule import im.vector.matrix.android.internal.di.SessionAssistedInjectModule
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.account.AccountModule
import im.vector.matrix.android.internal.session.cache.CacheModule import im.vector.matrix.android.internal.session.cache.CacheModule
import im.vector.matrix.android.internal.session.content.ContentModule import im.vector.matrix.android.internal.session.content.ContentModule
import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.content.UploadContentWorker
@ -55,24 +56,25 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@Component(dependencies = [MatrixComponent::class], @Component(dependencies = [MatrixComponent::class],
modules = [ modules = [
SessionModule::class, SessionModule::class,
RoomModule::class, RoomModule::class,
SyncModule::class, SyncModule::class,
HomeServerCapabilitiesModule::class, HomeServerCapabilitiesModule::class,
SignOutModule::class, SignOutModule::class,
GroupModule::class, GroupModule::class,
UserModule::class, UserModule::class,
FilterModule::class, FilterModule::class,
GroupModule::class, GroupModule::class,
ContentModule::class, ContentModule::class,
CacheModule::class, CacheModule::class,
CryptoModule::class, CryptoModule::class,
PushersModule::class, PushersModule::class,
AccountDataModule::class, AccountDataModule::class,
ProfileModule::class, ProfileModule::class,
SessionAssistedInjectModule::class SessionAssistedInjectModule::class,
] AccountModule::class
]
) )
@SessionScope @SessionScope
internal interface SessionComponent { internal interface SessionComponent {

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.account
import im.vector.matrix.android.api.session.account.model.ChangePasswordParams
import im.vector.matrix.android.internal.network.NetworkConstants
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST
internal interface AccountAPI {
/**
* Ask the homeserver to change the password with the provided new password.
* @param params parameters to change password.
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "account/password")
fun changePassword(@Body params: ChangePasswordParams): Call<Unit>
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.account
import dagger.Binds
import dagger.Module
import dagger.Provides
import im.vector.matrix.android.api.session.account.AccountService
import im.vector.matrix.android.internal.session.SessionScope
import retrofit2.Retrofit
@Module
internal abstract class AccountModule {
@Module
companion object {
@Provides
@JvmStatic
@SessionScope
fun providesAccountAPI(retrofit: Retrofit): AccountAPI {
return retrofit.create(AccountAPI::class.java)
}
}
@Binds
abstract fun bindChangePasswordTask(task: DefaultChangePasswordTask): ChangePasswordTask
@Binds
abstract fun bindAccountService(service: DefaultAccountService): AccountService
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.account
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.account.model.ChangePasswordParams
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task
import org.greenrobot.eventbus.EventBus
import javax.inject.Inject
internal interface ChangePasswordTask : Task<ChangePasswordTask.Params, Unit> {
data class Params(
val password: String,
val newPassword: String
)
}
internal class DefaultChangePasswordTask @Inject constructor(
private val accountAPI: AccountAPI,
private val eventBus: EventBus,
@UserId private val userId: String
) : ChangePasswordTask {
override suspend fun execute(params: ChangePasswordTask.Params) {
val changePasswordParams = ChangePasswordParams.create(userId, params.password, params.newPassword)
try {
executeRequest<Unit>(eventBus) {
apiCall = accountAPI.changePassword(changePasswordParams)
}
} catch (throwable: Throwable) {
if (throwable is Failure.OtherServerError
&& throwable.httpCode == 401
/* Avoid infinite loop */
&& changePasswordParams.auth?.session == null) {
try {
MoshiProvider.providesMoshi()
.adapter(RegistrationFlowResponse::class.java)
.fromJson(throwable.errorBody)
} catch (e: Exception) {
null
}?.let {
// Retry with authentication
try {
executeRequest<Unit>(eventBus) {
apiCall = accountAPI.changePassword(
changePasswordParams.copy(auth = changePasswordParams.auth?.copy(session = it.session))
)
}
return
} catch (failure: Throwable) {
throw failure
}
}
}
throw throwable
}
}
}

View File

@ -0,0 +1,36 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.account
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.account.AccountService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject
internal class DefaultAccountService @Inject constructor(private val changePasswordTask: ChangePasswordTask,
private val taskExecutor: TaskExecutor) : AccountService {
override fun changePassword(password: String, newPassword: String, callback: MatrixCallback<Unit>): Cancelable {
return changePasswordTask
.configureWith(ChangePasswordTask.Params(password, newPassword)) {
this.callback = callback
}
.executeBy(taskExecutor)
}
}

View File

@ -22,6 +22,12 @@ import retrofit2.http.GET
internal interface CapabilitiesAPI { internal interface CapabilitiesAPI {
/**
* Request the homeserver capabilities
*/
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "capabilities")
fun getCapabilities(): Call<GetCapabilitiesResult>
/** /**
* Request the upload capabilities * Request the upload capabilities
*/ */

View File

@ -51,15 +51,23 @@ internal class DefaultGetHomeServerCapabilitiesTask @Inject constructor(
apiCall = capabilitiesAPI.getUploadCapabilities() apiCall = capabilitiesAPI.getUploadCapabilities()
} }
val capabilities = runCatching {
executeRequest<GetCapabilitiesResult>(eventBus) {
apiCall = capabilitiesAPI.getCapabilities()
}
}.getOrNull()
// TODO Add other call here (get version, etc.) // TODO Add other call here (get version, etc.)
insertInDb(uploadCapabilities) insertInDb(capabilities, uploadCapabilities)
} }
private suspend fun insertInDb(getUploadCapabilitiesResult: GetUploadCapabilitiesResult) { private suspend fun insertInDb(getCapabilitiesResult: GetCapabilitiesResult?, getUploadCapabilitiesResult: GetUploadCapabilitiesResult) {
monarchy.awaitTransaction { realm -> monarchy.awaitTransaction { realm ->
val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm) val homeServerCapabilitiesEntity = HomeServerCapabilitiesEntity.getOrCreate(realm)
homeServerCapabilitiesEntity.canChangePassword = getCapabilitiesResult.canChangePassword()
homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize homeServerCapabilitiesEntity.maxUploadFileSize = getUploadCapabilitiesResult.maxUploadSize
?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN ?: HomeServerCapabilities.MAX_UPLOAD_FILE_SIZE_UNKNOWN

View File

@ -0,0 +1,58 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.internal.session.homeserver
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.extensions.orTrue
/**
* Ref: https://matrix.org/docs/spec/client_server/latest#get-matrix-client-r0-capabilities
*/
@JsonClass(generateAdapter = true)
internal data class GetCapabilitiesResult(
/**
* Required. The custom capabilities the server supports, using the Java package naming convention.
*/
@Json(name = "capabilities")
val capabilities: Capabilities? = null
)
@JsonClass(generateAdapter = true)
internal data class Capabilities(
/**
* Capability to indicate if the user can change their password.
*/
@Json(name = "m.change_password")
val changePassword: ChangePassword? = null
// No need for m.room_versions for the moment
)
@JsonClass(generateAdapter = true)
internal data class ChangePassword(
/**
* Required. True if the user can change their password, false otherwise.
*/
@Json(name = "enabled")
val enabled: Boolean?
)
// The spec says: If not present, the client should assume that password changes are possible via the API
internal fun GetCapabilitiesResult?.canChangePassword(): Boolean {
return this?.capabilities?.changePassword?.enabled.orTrue()
}

View File

@ -20,7 +20,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class GetUploadCapabilitiesResult( internal data class GetUploadCapabilitiesResult(
/** /**
* The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content. * The maximum size an upload can be in bytes. Clients SHOULD use this as a guide when uploading content.
* If not listed or null, the size limit should be treated as unknown. * If not listed or null, the size limit should be treated as unknown.

View File

@ -314,7 +314,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType = MessageType.MSGTYPE_AUDIO, msgType = MessageType.MSGTYPE_AUDIO,
body = attachment.name ?: "audio", body = attachment.name ?: "audio",
audioInfo = AudioInfo( audioInfo = AudioInfo(
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } ?: "audio/mpeg", mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() },
size = attachment.size size = attachment.size
), ),
url = attachment.queryUri.toString() url = attachment.queryUri.toString()
@ -327,8 +327,7 @@ internal class LocalEchoEventFactory @Inject constructor(
msgType = MessageType.MSGTYPE_FILE, msgType = MessageType.MSGTYPE_FILE,
body = attachment.name ?: "file", body = attachment.name ?: "file",
info = FileInfo( info = FileInfo(
mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() } mimeType = attachment.getSafeMimeType()?.takeIf { it.isNotBlank() },
?: "application/octet-stream",
size = attachment.size size = attachment.size
), ),
url = attachment.queryUri.toString() url = attachment.queryUri.toString()

View File

@ -18,6 +18,7 @@ package im.vector.riotx.core.error
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.failure.isInvalidPassword
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import java.net.HttpURLConnection import java.net.HttpURLConnection
@ -54,8 +55,7 @@ class DefaultErrorFormatter @Inject constructor(
// Special case for terms and conditions // Special case for terms and conditions
stringProvider.getString(R.string.error_terms_not_accepted) stringProvider.getString(R.string.error_terms_not_accepted)
} }
throwable.error.code == MatrixError.M_FORBIDDEN throwable.isInvalidPassword() -> {
&& throwable.error.message == "Invalid password" -> {
stringProvider.getString(R.string.auth_invalid_login_param) stringProvider.getString(R.string.auth_invalid_login_param)
} }
throwable.error.code == MatrixError.M_USER_IN_USE -> { throwable.error.code == MatrixError.M_USER_IN_USE -> {
@ -67,7 +67,7 @@ class DefaultErrorFormatter @Inject constructor(
throwable.error.code == MatrixError.M_NOT_JSON -> { throwable.error.code == MatrixError.M_NOT_JSON -> {
stringProvider.getString(R.string.login_error_not_json) stringProvider.getString(R.string.login_error_not_json)
} }
throwable.error.code == MatrixError.M_THREEPID_DENIED -> { throwable.error.code == MatrixError.M_THREEPID_DENIED -> {
stringProvider.getString(R.string.login_error_threepid_denied) stringProvider.getString(R.string.login_error_threepid_denied)
} }
throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> { throwable.error.code == MatrixError.M_LIMIT_EXCEEDED -> {

View File

@ -23,6 +23,7 @@ import android.os.Parcelable
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager
import androidx.annotation.AttrRes import androidx.annotation.AttrRes
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.MainThread import androidx.annotation.MainThread
@ -183,6 +184,11 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
handleGlobalError(it) handleGlobalError(it)
} }
// Set flag FLAG_SECURE
if (vectorPreferences.useFlagSecure()) {
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
}
doBeforeSetContentView() doBeforeSetContentView()
if (getLayoutRes() != -1) { if (getLayoutRes() != -1) {

View File

@ -28,6 +28,7 @@ import com.airbnb.mvrx.Success
import com.jakewharton.rxbinding3.widget.textChanges import com.jakewharton.rxbinding3.widget.textChanges
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.failure.isInvalidPassword
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.extensions.showPassword
@ -209,10 +210,7 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
} else { } else {
// Trick to display the error without text. // Trick to display the error without text.
loginFieldTil.error = " " loginFieldTil.error = " "
if (error is Failure.ServerError if (error.isInvalidPassword() && spaceInPassword()) {
&& error.error.code == MatrixError.M_FORBIDDEN
&& error.error.message == "Invalid password"
&& spaceInPassword()) {
passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password) passwordFieldTil.error = getString(R.string.auth_invalid_login_param_space_in_password)
} else { } else {
passwordFieldTil.error = errorFormatter.toHumanReadable(error) passwordFieldTil.error = errorFormatter.toHumanReadable(error)

View File

@ -150,6 +150,9 @@ class VectorPreferences @Inject constructor(private val context: Context) {
const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY" const val SETTINGS_USE_RAGE_SHAKE_KEY = "SETTINGS_USE_RAGE_SHAKE_KEY"
const val SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY = "SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY" const val SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY = "SETTINGS_RAGE_SHAKE_DETECTION_THRESHOLD_KEY"
// Security
const val SETTINGS_SECURITY_USE_FLAG_SECURE = "SETTINGS_SECURITY_USE_FLAG_SECURE"
// other // other
const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY" const val SETTINGS_MEDIA_SAVING_PERIOD_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_KEY"
private const val SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY" private const val SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY = "SETTINGS_MEDIA_SAVING_PERIOD_SELECTED_KEY"
@ -199,7 +202,8 @@ class VectorPreferences @Inject constructor(private val context: Context) {
SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY, SETTINGS_SET_SYNC_TIMEOUT_PREFERENCE_KEY,
SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY, SETTINGS_SET_SYNC_DELAY_PREFERENCE_KEY,
SETTINGS_USE_RAGE_SHAKE_KEY SETTINGS_USE_RAGE_SHAKE_KEY,
SETTINGS_SECURITY_USE_FLAG_SECURE
) )
} }
@ -746,4 +750,11 @@ class VectorPreferences @Inject constructor(private val context: Context) {
fun displayAllEvents(): Boolean { fun displayAllEvents(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false) return defaultPrefs.getBoolean(SETTINGS_DISPLAY_ALL_EVENTS_KEY, false)
} }
/**
* The user does not allow screenshots of the application
*/
fun useFlagSecure(): Boolean {
return defaultPrefs.getBoolean(SETTINGS_SECURITY_USE_FLAG_SECURE, false)
}
} }

View File

@ -35,6 +35,8 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.cache.DiskCache import com.bumptech.glide.load.engine.cache.DiskCache
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.isInvalidPassword
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.extensions.hideKeyboard import im.vector.riotx.core.extensions.hideKeyboard
import im.vector.riotx.core.extensions.showPassword import im.vector.riotx.core.extensions.showPassword
@ -108,10 +110,14 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
} }
// Password // Password
mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener { // Hide the preference if password can not be updated
notImplemented() if (session.getHomeServerCapabilities().canChangePassword) {
// onPasswordUpdateClick() mPasswordPreference.onPreferenceClickListener = Preference.OnPreferenceClickListener {
false onPasswordUpdateClick()
false
}
} else {
mPasswordPreference.isVisible = false
} }
// Add Email // Add Email
@ -684,21 +690,20 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
var passwordShown = false var passwordShown = false
showPassword.setOnClickListener(object : View.OnClickListener { showPassword.setOnClickListener {
override fun onClick(v: View?) { passwordShown = !passwordShown
passwordShown = !passwordShown
oldPasswordText.showPassword(passwordShown) oldPasswordText.showPassword(passwordShown)
newPasswordText.showPassword(passwordShown) newPasswordText.showPassword(passwordShown)
confirmNewPasswordText.showPassword(passwordShown) confirmNewPasswordText.showPassword(passwordShown)
showPassword.setImageResource(if (passwordShown) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black) showPassword.setImageResource(if (passwordShown) R.drawable.ic_eye_closed_black else R.drawable.ic_eye_black)
} }
})
val dialog = AlertDialog.Builder(activity) val dialog = AlertDialog.Builder(activity)
.setView(view) .setView(view)
.setPositiveButton(R.string.settings_change_password_submit, null) .setCancelable(false)
.setPositiveButton(R.string.settings_change_password, null)
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setOnDismissListener { .setOnDismissListener {
view.hideKeyboard() view.hideKeyboard()
@ -707,12 +712,13 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
dialog.setOnShowListener { dialog.setOnShowListener {
val updateButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE) val updateButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE)
val cancelButton = dialog.getButton(AlertDialog.BUTTON_NEGATIVE)
updateButton.isEnabled = false updateButton.isEnabled = false
fun updateUi() { fun updateUi() {
val oldPwd = oldPasswordText.text.toString().trim() val oldPwd = oldPasswordText.text.toString()
val newPwd = newPasswordText.text.toString().trim() val newPwd = newPasswordText.text.toString()
val newConfirmPwd = confirmNewPasswordText.text.toString().trim() val newConfirmPwd = confirmNewPasswordText.text.toString()
updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd updateButton.isEnabled = oldPwd.isNotEmpty() && newPwd.isNotEmpty() && newPwd == newConfirmPwd
@ -750,6 +756,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
confirmNewPasswordText.isEnabled = false confirmNewPasswordText.isEnabled = false
changePasswordLoader.isVisible = true changePasswordLoader.isVisible = true
updateButton.isEnabled = false updateButton.isEnabled = false
cancelButton.isEnabled = false
} else { } else {
showPassword.isEnabled = true showPassword.isEnabled = true
oldPasswordText.isEnabled = true oldPasswordText.isEnabled = true
@ -757,6 +764,7 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
confirmNewPasswordText.isEnabled = true confirmNewPasswordText.isEnabled = true
changePasswordLoader.isVisible = false changePasswordLoader.isVisible = false
updateButton.isEnabled = true updateButton.isEnabled = true
cancelButton.isEnabled = true
} }
} }
@ -768,47 +776,32 @@ class VectorSettingsGeneralFragment : VectorSettingsBaseFragment() {
view.hideKeyboard() view.hideKeyboard()
val oldPwd = oldPasswordText.text.toString().trim() val oldPwd = oldPasswordText.text.toString()
val newPwd = newPasswordText.text.toString().trim() val newPwd = newPasswordText.text.toString()
notImplemented()
/* TODO
showPasswordLoadingView(true) showPasswordLoadingView(true)
session.changePassword(oldPwd, newPwd, object : MatrixCallback<Unit> {
session.updatePassword(oldPwd, newPwd, object : MatrixCallback<Unit> { override fun onSuccess(data: Unit) {
private fun onDone(@StringRes textResId: Int) { if (!isAdded) {
return
}
showPasswordLoadingView(false) showPasswordLoadingView(false)
dialog.dismiss()
activity.toast(R.string.settings_password_updated)
}
if (textResId == R.string.settings_fail_to_update_password_invalid_current_password) { override fun onFailure(failure: Throwable) {
oldPasswordTil.error = getString(textResId) if (!isAdded) {
} else { return
dialog.dismiss()
activity.toast(textResId, Toast.LENGTH_LONG)
} }
} showPasswordLoadingView(false)
if (failure.isInvalidPassword()) {
override fun onSuccess(info: Void?) { oldPasswordTil.error = getString(R.string.settings_fail_to_update_password_invalid_current_password)
onDone(R.string.settings_password_updated)
}
override fun onNetworkError(e: Exception) {
onDone(R.string.settings_fail_to_update_password)
}
override fun onMatrixError(e: MatrixError) {
if (e.error == "Invalid password") {
onDone(R.string.settings_fail_to_update_password_invalid_current_password)
} else { } else {
dialog.dismiss() oldPasswordTil.error = getString(R.string.settings_fail_to_update_password)
onDone(R.string.settings_fail_to_update_password)
} }
} }
override fun onUnexpectedError(e: Exception) {
onDone(R.string.settings_fail_to_update_password)
}
}) })
*/
} }
} }
dialog.show() dialog.show()

View File

@ -39,7 +39,8 @@
android:id="@+id/change_password_old_pwd_til" android:id="@+id/change_password_old_pwd_til"
style="@style/VectorTextInputLayout" style="@style/VectorTextInputLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/change_password_old_pwd_text" android:id="@+id/change_password_old_pwd_text"
@ -53,7 +54,9 @@
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
style="@style/VectorTextInputLayout" style="@style/VectorTextInputLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:id="@+id/change_password_new_pwd_text" android:id="@+id/change_password_new_pwd_text"
@ -69,6 +72,7 @@
style="@style/VectorTextInputLayout" style="@style/VectorTextInputLayout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:errorEnabled="true"> app:errorEnabled="true">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText

View File

@ -14,6 +14,12 @@
<!-- END Strings added by Benoit --> <!-- END Strings added by Benoit -->
<!-- BEGIN Strings added by Benoit2 -->
<string name="settings_security_prevent_screenshots_title">Prevent screenshots of the application</string>
<string name="settings_security_prevent_screenshots_summary">Enabling this setting adds the FLAG_SECURE to all Activities. Restart the application for the change to take effect.</string>
<!-- END Strings added by Benoit2 -->
<!-- BEGIN Strings added by Ganfra --> <!-- BEGIN Strings added by Ganfra -->
@ -29,4 +35,7 @@
<!-- END Strings added by Others --> <!-- END Strings added by Others -->
<!-- BEGIN Strings added by Benoit -->
<string name="change_password_summary">Set a new account password…</string>
<!---->
</resources> </resources>

View File

@ -19,7 +19,7 @@
<im.vector.riotx.core.preference.VectorPreference <im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY" android:key="SETTINGS_CHANGE_PASSWORD_PREFERENCE_KEY"
android:summary="@string/password_hint" android:summary="@string/change_password_summary"
android:title="@string/settings_password" /> android:title="@string/settings_password" />
<!-- Email will be added here --> <!-- Email will be added here -->

View File

@ -6,36 +6,35 @@
<!-- ************ Cryptography section ************ --> <!-- ************ Cryptography section ************ -->
<im.vector.riotx.core.preference.VectorPreferenceCategory <im.vector.riotx.core.preference.VectorPreferenceCategory
android:key="SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY" android:key="SETTINGS_CRYPTOGRAPHY_PREFERENCE_KEY"
tools:isPreferenceVisible="true" android:title="@string/settings_cryptography"
app:isPreferenceVisible="false" app:isPreferenceVisible="false"
android:title="@string/settings_cryptography"> tools:isPreferenceVisible="true">
<im.vector.riotx.core.preference.VectorPreference <im.vector.riotx.core.preference.VectorPreference
android:key="SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY" android:key="SETTINGS_ENCRYPTION_CROSS_SIGNING_PREFERENCE_KEY"
tools:icon="@drawable/ic_shield_trusted"
android:persistent="false" android:persistent="false"
android:title="@string/encryption_information_cross_signing_state" android:title="@string/encryption_information_cross_signing_state"
tools:summary="@string/encryption_information_dg_xsigning_complete"
app:fragment="im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragment" app:fragment="im.vector.riotx.features.settings.crosssigning.CrossSigningSettingsFragment"
/> tools:icon="@drawable/ic_shield_trusted"
tools:summary="@string/encryption_information_dg_xsigning_complete" />
<!-- <im.vector.riotx.core.preference.VectorPreference--> <!-- <im.vector.riotx.core.preference.VectorPreference-->
<!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"--> <!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_NAME_PREFERENCE_KEY"-->
<!-- android:title="@string/encryption_information_device_name" />--> <!-- android:title="@string/encryption_information_device_name" />-->
<!-- <im.vector.riotx.core.preference.VectorPreference--> <!-- <im.vector.riotx.core.preference.VectorPreference-->
<!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"--> <!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_ID_PREFERENCE_KEY"-->
<!-- android:title="@string/encryption_information_device_id" />--> <!-- android:title="@string/encryption_information_device_id" />-->
<!-- <im.vector.riotx.core.preference.VectorPreference--> <!-- <im.vector.riotx.core.preference.VectorPreference-->
<!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY"--> <!-- android:key="SETTINGS_ENCRYPTION_INFORMATION_DEVICE_KEY_PREFERENCE_KEY"-->
<!-- android:title="@string/encryption_information_device_key" />--> <!-- android:title="@string/encryption_information_device_key" />-->
<im.vector.riotx.core.preference.VectorSwitchPreference <im.vector.riotx.core.preference.VectorSwitchPreference
android:enabled="false"
android:key="SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY" android:key="SETTINGS_ENCRYPTION_NEVER_SENT_TO_PREFERENCE_KEY"
android:summary="@string/encryption_never_send_to_unverified_devices_summary" android:summary="@string/encryption_never_send_to_unverified_devices_summary"
android:title="@string/encryption_never_send_to_unverified_devices_title" android:title="@string/encryption_never_send_to_unverified_devices_title" />
android:enabled="false" />
</im.vector.riotx.core.preference.VectorPreferenceCategory> </im.vector.riotx.core.preference.VectorPreferenceCategory>
@ -85,4 +84,15 @@
</im.vector.riotx.core.preference.VectorPreferenceCategory> </im.vector.riotx.core.preference.VectorPreferenceCategory>
<im.vector.riotx.core.preference.VectorPreferenceCategory
android:title="@string/settings_other">
<im.vector.riotx.core.preference.VectorSwitchPreference
android:defaultValue="false"
android:key="SETTINGS_SECURITY_USE_FLAG_SECURE"
android:summary="@string/settings_security_prevent_screenshots_summary"
android:title="@string/settings_security_prevent_screenshots_title" />
</im.vector.riotx.core.preference.VectorPreferenceCategory>
</androidx.preference.PreferenceScreen> </androidx.preference.PreferenceScreen>