mirror of
https://github.com/element-hq/element-android
synced 2024-11-28 05:31:21 +03:00
Merge branch 'develop' into feature/realm_entity_rework
This commit is contained in:
commit
4521ea14ee
32 changed files with 394 additions and 524 deletions
|
@ -14,7 +14,7 @@ steps:
|
|||
- "./gradlew clean lintGplayRelease assembleGplayDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/gplay/debug/*.apk"
|
||||
branches: "develop feature/*"
|
||||
branches: "!master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
@ -28,7 +28,7 @@ steps:
|
|||
- "./gradlew clean lintFdroidRelease assembleFdroidDebug --stacktrace"
|
||||
artifact_paths:
|
||||
- "vector/build/outputs/apk/fdroid/debug/*.apk"
|
||||
branches: "develop feature/*"
|
||||
branches: "!master"
|
||||
plugins:
|
||||
- docker#v3.1.0:
|
||||
image: "runmymind/docker-android-sdk"
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.matrix.android.api
|
||||
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
* This class contains pattern to match the different Matrix ids
|
||||
|
@ -29,31 +28,31 @@ object MatrixPatterns {
|
|||
// regex pattern to find matrix user ids in a string.
|
||||
// See https://matrix.org/speculator/spec/HEAD/appendices.html#historical-user-ids
|
||||
private const val MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9\\x21-\\x39\\x3B-\\x7F]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = MATRIX_USER_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find room ids in a string.
|
||||
private const val MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = MATRIX_ROOM_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find room aliases in a string.
|
||||
private const val MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#@=+-]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_ALIAS = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_ALIAS = MATRIX_ROOM_ALIAS_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find message ids in a string.
|
||||
private const val MATRIX_EVENT_IDENTIFIER_REGEX = "\\$[A-Z0-9]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = Pattern.compile(MATRIX_EVENT_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER = MATRIX_EVENT_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find message ids in a string.
|
||||
private const val MATRIX_EVENT_IDENTIFIER_V3_REGEX = "\\$[A-Z0-9/+]+"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V3_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3 = MATRIX_EVENT_IDENTIFIER_V3_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// Ref: https://matrix.org/docs/spec/rooms/v4#event-ids
|
||||
private const val MATRIX_EVENT_IDENTIFIER_V4_REGEX = "\\$[A-Z0-9\\-_]+"
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = Pattern.compile(MATRIX_EVENT_IDENTIFIER_V4_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4 = MATRIX_EVENT_IDENTIFIER_V4_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find group ids in a string.
|
||||
private const val MATRIX_GROUP_IDENTIFIER_REGEX = "\\+[A-Z0-9=_\\-./]+$DOMAIN_REGEX"
|
||||
private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = Pattern.compile(MATRIX_GROUP_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER = MATRIX_GROUP_IDENTIFIER_REGEX.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// regex pattern to find permalink with message id.
|
||||
// Android does not support in URL so extract it.
|
||||
|
@ -62,16 +61,16 @@ object MatrixPatterns {
|
|||
const val SEP_REGEX = "/"
|
||||
|
||||
private const val LINK_TO_ROOM_ID_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = LINK_TO_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
private const val LINK_TO_ROOM_ALIAS_REGEXP = PERMALINK_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = LINK_TO_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
private const val LINK_TO_APP_ROOM_ID_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_IDENTIFIER_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = Pattern.compile(LINK_TO_APP_ROOM_ID_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = LINK_TO_APP_ROOM_ID_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
private const val LINK_TO_APP_ROOM_ALIAS_REGEXP = APP_BASE_REGEX + MATRIX_ROOM_ALIAS_REGEX + SEP_REGEX + MATRIX_EVENT_IDENTIFIER_REGEX
|
||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = Pattern.compile(LINK_TO_APP_ROOM_ALIAS_REGEXP, Pattern.CASE_INSENSITIVE)
|
||||
private val PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = LINK_TO_APP_ROOM_ALIAS_REGEXP.toRegex(RegexOption.IGNORE_CASE)
|
||||
|
||||
// list of patterns to find some matrix item.
|
||||
val MATRIX_PATTERNS = listOf(
|
||||
|
@ -93,7 +92,7 @@ object MatrixPatterns {
|
|||
* @return true if the string is a valid user id
|
||||
*/
|
||||
fun isUserId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,7 +102,7 @@ object MatrixPatterns {
|
|||
* @return true if the string is a valid room Id
|
||||
*/
|
||||
fun isRoomId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -113,7 +112,7 @@ object MatrixPatterns {
|
|||
* @return true if the string is a valid room alias.
|
||||
*/
|
||||
fun isRoomAlias(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_ALIAS
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,9 +123,9 @@ object MatrixPatterns {
|
|||
*/
|
||||
fun isEventId(str: String?): Boolean {
|
||||
return str != null
|
||||
&& (PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER.matcher(str).matches()
|
||||
|| PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3.matcher(str).matches()
|
||||
|| PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4.matcher(str).matches())
|
||||
&& (str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER
|
||||
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V3
|
||||
|| str matches PATTERN_CONTAIN_MATRIX_EVENT_IDENTIFIER_V4)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,6 +135,6 @@ object MatrixPatterns {
|
|||
* @return true if the string is a valid group id.
|
||||
*/
|
||||
fun isGroupId(str: String?): Boolean {
|
||||
return str != null && PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER.matcher(str).matches()
|
||||
return str != null && str matches PATTERN_CONTAIN_MATRIX_GROUP_IDENTIFIER
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
|||
|
||||
data class RegistrationFlowError(val registrationFlowResponse: RegistrationFlowResponse) : Failure(RuntimeException(registrationFlowResponse.toString()))
|
||||
|
||||
data class CryptoError(val error: MXCryptoError) : Failure(RuntimeException(error.toString()))
|
||||
data class CryptoError(val error: MXCryptoError) : Failure(error)
|
||||
|
||||
abstract class FeatureFailure : Failure()
|
||||
|
||||
|
|
|
@ -37,15 +37,13 @@ object MatrixLinkify {
|
|||
}
|
||||
val text = spannable.toString()
|
||||
var hasMatch = false
|
||||
for (index in MatrixPatterns.MATRIX_PATTERNS.indices) {
|
||||
val pattern = MatrixPatterns.MATRIX_PATTERNS[index]
|
||||
val matcher = pattern.matcher(spannable)
|
||||
while (matcher.find()) {
|
||||
for (pattern in MatrixPatterns.MATRIX_PATTERNS) {
|
||||
for (match in pattern.findAll(spannable)) {
|
||||
hasMatch = true
|
||||
val startPos = matcher.start(0)
|
||||
val startPos = match.range.first
|
||||
if (startPos == 0 || text[startPos - 1] != '/') {
|
||||
val endPos = matcher.end(0)
|
||||
val url = text.substring(matcher.start(0), matcher.end(0))
|
||||
val endPos = match.range.last
|
||||
val url = text.substring(match.range)
|
||||
val span = MatrixPermalinkSpan(url, callback)
|
||||
spannable.setSpan(span, startPos, endPos, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||
}
|
||||
|
|
|
@ -98,9 +98,10 @@ interface CryptoService {
|
|||
roomId: String,
|
||||
callback: MatrixCallback<MXEncryptEventContentResult>)
|
||||
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult?
|
||||
@Throws(MXCryptoError::class)
|
||||
fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
|
||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>)
|
||||
fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>)
|
||||
|
||||
fun getEncryptionAlgorithm(roomId: String): String?
|
||||
|
||||
|
|
|
@ -18,108 +18,58 @@
|
|||
|
||||
package im.vector.matrix.android.api.session.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
import org.matrix.olm.OlmException
|
||||
|
||||
/**
|
||||
* Represents a crypto error response.
|
||||
*/
|
||||
class MXCryptoError(var code: String,
|
||||
var message: String) {
|
||||
sealed class MXCryptoError : Throwable() {
|
||||
|
||||
/**
|
||||
* Describe the error with more details
|
||||
*/
|
||||
private var mDetailedErrorDescription: String? = null
|
||||
data class Base(val errorType: ErrorType,
|
||||
val technicalMessage: String,
|
||||
/**
|
||||
* Describe the error with more details
|
||||
*/
|
||||
val detailedErrorDescription: String? = null) : MXCryptoError()
|
||||
|
||||
/**
|
||||
* Data exception.
|
||||
* Some exceptions provide some data to describe the exception
|
||||
*/
|
||||
var mExceptionData: Any? = null
|
||||
data class OlmError(val olmException: OlmException) : MXCryptoError()
|
||||
|
||||
/**
|
||||
* @return true if the current error is an olm one.
|
||||
*/
|
||||
val isOlmError: Boolean
|
||||
get() = OLM_ERROR_CODE == code
|
||||
data class UnknownDevice(val deviceList: MXUsersDevicesMap<MXDeviceInfo>) : MXCryptoError()
|
||||
|
||||
|
||||
/**
|
||||
* @return the detailed error description
|
||||
*/
|
||||
val detailedErrorDescription: String?
|
||||
get() = if (TextUtils.isEmpty(mDetailedErrorDescription)) {
|
||||
message
|
||||
} else mDetailedErrorDescription
|
||||
|
||||
/**
|
||||
* Create a crypto error
|
||||
*
|
||||
* @param code the error code (see XX_ERROR_CODE)
|
||||
* @param shortErrorDescription the short error description
|
||||
* @param detailedErrorDescription the detailed error description
|
||||
*/
|
||||
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?) : this(code, shortErrorDescription) {
|
||||
mDetailedErrorDescription = detailedErrorDescription
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a crypto error
|
||||
*
|
||||
* @param code the error code (see XX_ERROR_CODE)
|
||||
* @param shortErrorDescription the short error description
|
||||
* @param detailedErrorDescription the detailed error description
|
||||
* @param exceptionData the exception data
|
||||
*/
|
||||
constructor(code: String, shortErrorDescription: String, detailedErrorDescription: String?, exceptionData: Any) : this(code, shortErrorDescription) {
|
||||
mDetailedErrorDescription = detailedErrorDescription
|
||||
mExceptionData = exceptionData
|
||||
enum class ErrorType {
|
||||
ENCRYPTING_NOT_ENABLED,
|
||||
UNABLE_TO_ENCRYPT,
|
||||
UNABLE_TO_DECRYPT,
|
||||
UNKNOWN_INBOUND_SESSION_ID,
|
||||
INBOUND_SESSION_MISMATCH_ROOM_ID,
|
||||
MISSING_FIELDS,
|
||||
BAD_EVENT_FORMAT,
|
||||
MISSING_SENDER_KEY,
|
||||
MISSING_CIPHER_TEXT,
|
||||
BAD_DECRYPTED_FORMAT,
|
||||
NOT_INCLUDE_IN_RECIPIENTS,
|
||||
BAD_RECIPIENT,
|
||||
BAD_RECIPIENT_KEY,
|
||||
FORWARDED_MESSAGE,
|
||||
BAD_ROOM,
|
||||
BAD_ENCRYPTED_MESSAGE,
|
||||
DUPLICATED_MESSAGE_INDEX,
|
||||
MISSING_PROPERTY,
|
||||
OLM,
|
||||
UNKNOWN_DEVICES,
|
||||
UNKNOWN_MESSAGE_INDEX
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
// TODO Create sealed class
|
||||
|
||||
/**
|
||||
* Error codes
|
||||
* Resource for technicalMessage
|
||||
*/
|
||||
const val UNKNOWN_ERROR_CODE = "UNKNOWN_ERROR_CODE"
|
||||
const val ENCRYPTING_NOT_ENABLED_ERROR_CODE = "ENCRYPTING_NOT_ENABLED"
|
||||
const val UNABLE_TO_ENCRYPT_ERROR_CODE = "UNABLE_TO_ENCRYPT"
|
||||
const val UNABLE_TO_DECRYPT_ERROR_CODE = "UNABLE_TO_DECRYPT"
|
||||
const val UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE = "UNKNOWN_INBOUND_SESSION_ID"
|
||||
const val INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE = "INBOUND_SESSION_MISMATCH_ROOM_ID"
|
||||
const val MISSING_FIELDS_ERROR_CODE = "MISSING_FIELDS"
|
||||
const val BAD_EVENT_FORMAT_ERROR_CODE = "BAD_EVENT_FORMAT_ERROR_CODE"
|
||||
const val MISSING_SENDER_KEY_ERROR_CODE = "MISSING_SENDER_KEY_ERROR_CODE"
|
||||
const val MISSING_CIPHER_TEXT_ERROR_CODE = "MISSING_CIPHER_TEXT"
|
||||
const val BAD_DECRYPTED_FORMAT_ERROR_CODE = "BAD_DECRYPTED_FORMAT_ERROR_CODE"
|
||||
const val NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE = "NOT_INCLUDE_IN_RECIPIENTS"
|
||||
const val BAD_RECIPIENT_ERROR_CODE = "BAD_RECIPIENT"
|
||||
const val BAD_RECIPIENT_KEY_ERROR_CODE = "BAD_RECIPIENT_KEY"
|
||||
const val FORWARDED_MESSAGE_ERROR_CODE = "FORWARDED_MESSAGE"
|
||||
const val BAD_ROOM_ERROR_CODE = "BAD_ROOM"
|
||||
const val BAD_ENCRYPTED_MESSAGE_ERROR_CODE = "BAD_ENCRYPTED_MESSAGE"
|
||||
const val DUPLICATED_MESSAGE_INDEX_ERROR_CODE = "DUPLICATED_MESSAGE_INDEX"
|
||||
const val MISSING_PROPERTY_ERROR_CODE = "MISSING_PROPERTY"
|
||||
const val OLM_ERROR_CODE = "OLM_ERROR_CODE"
|
||||
const val UNKNOWN_DEVICES_CODE = "UNKNOWN_DEVICES_CODE"
|
||||
const val UNKNOWN_MESSAGE_INDEX = "UNKNOWN_MESSAGE_INDEX"
|
||||
|
||||
/**
|
||||
* short error reasons
|
||||
*/
|
||||
const val UNABLE_TO_DECRYPT = "Unable to decrypt"
|
||||
const val UNABLE_TO_ENCRYPT = "Unable to encrypt"
|
||||
|
||||
/**
|
||||
* Detailed error reasons
|
||||
*/
|
||||
const val ENCRYPTING_NOT_ENABLED_REASON = "Encryption not enabled"
|
||||
const val UNABLE_TO_ENCRYPT_REASON = "Unable to encrypt %s"
|
||||
const val UNABLE_TO_DECRYPT_REASON = "Unable to decrypt %1\$s. Algorithm: %2\$s"
|
||||
const val OLM_REASON = "OLM error: %1\$s"
|
||||
const val DETAILLED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s"
|
||||
const val DETAILED_OLM_REASON = "Unable to decrypt %1\$s. OLM error: %2\$s"
|
||||
const val UNKNOWN_INBOUND_SESSION_ID_REASON = "Unknown inbound session id"
|
||||
const val INBOUND_SESSION_MISMATCH_ROOM_ID_REASON = "Mismatched room_id for inbound group session (expected %1\$s, was %2\$s)"
|
||||
const val MISSING_FIELDS_REASON = "Missing fields in input"
|
||||
|
@ -138,4 +88,4 @@ class MXCryptoError(var code: String,
|
|||
const val UNKNOWN_DEVICES_REASON = "This room contains unknown devices which have not been verified.\n" + "We strongly recommend you verify them before continuing."
|
||||
const val NO_MORE_ALGORITHM_REASON = "Room was previously configured to use encryption, but is no longer." + " Perhaps the homeserver is hiding the configuration event."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -140,37 +140,32 @@ data class Event(
|
|||
*
|
||||
* @param decryptionResult the decryption result, including the plaintext and some key info.
|
||||
*/
|
||||
internal fun setClearData(decryptionResult: MXEventDecryptionResult?) {
|
||||
internal fun setClearData(decryptionResult: MXEventDecryptionResult) {
|
||||
mClearEvent = null
|
||||
if (decryptionResult != null) {
|
||||
if (decryptionResult.clearEvent != null) {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||
mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
|
||||
mCryptoError = null
|
||||
|
||||
if (mClearEvent != null) {
|
||||
mSenderCurve25519Key = decryptionResult.senderCurve25519Key
|
||||
mClaimedEd25519Key = decryptionResult.claimedEd25519Key
|
||||
mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Event::class.java)
|
||||
mClearEvent = adapter.fromJsonValue(decryptionResult.clearEvent)
|
||||
|
||||
// For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
|
||||
// in the clear event
|
||||
try {
|
||||
content?.get("m.relates_to")?.let { clearRelates ->
|
||||
mClearEvent = mClearEvent?.copy(
|
||||
content = HashMap(mClearEvent!!.content).apply {
|
||||
this["m.relates_to"] = clearRelates
|
||||
}
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
|
||||
}
|
||||
if (mClearEvent != null) {
|
||||
mSenderCurve25519Key = decryptionResult.senderCurve25519Key
|
||||
mClaimedEd25519Key = decryptionResult.claimedEd25519Key
|
||||
mForwardingCurve25519KeyChain = decryptionResult.forwardingCurve25519KeyChain
|
||||
|
||||
// For encrypted events with relation, the m.relates_to is kept in clear, so we need to put it back
|
||||
// in the clear event
|
||||
try {
|
||||
content?.get("m.relates_to")?.let { clearRelates ->
|
||||
mClearEvent = mClearEvent?.copy(
|
||||
content = HashMap(mClearEvent!!.content).apply {
|
||||
this["m.relates_to"] = clearRelates
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "Unable to restore 'm.relates_to' the clear event")
|
||||
}
|
||||
}
|
||||
mCryptoError = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,20 +16,8 @@
|
|||
|
||||
package im.vector.matrix.android.api.util
|
||||
|
||||
class CancelableBag : Cancelable {
|
||||
|
||||
private val cancelableList = ArrayList<Cancelable>()
|
||||
|
||||
fun add(cancelable: Cancelable) {
|
||||
cancelableList.add(cancelable)
|
||||
}
|
||||
|
||||
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
|
||||
override fun cancel() {
|
||||
cancelableList.forEach { it.cancel() }
|
||||
forEach { it.cancel() }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun Cancelable.addTo(cancelables: CancelableBag) {
|
||||
cancelables.add(this)
|
||||
}
|
|
@ -21,13 +21,4 @@ import im.vector.matrix.android.api.MatrixCallback
|
|||
/**
|
||||
* Simple MatrixCallback implementation which delegate its calls to another callback
|
||||
*/
|
||||
open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> {
|
||||
|
||||
override fun onSuccess(data: T) {
|
||||
callback.onSuccess(data)
|
||||
}
|
||||
|
||||
override fun onFailure(failure: Throwable) {
|
||||
callback.onFailure(failure)
|
||||
}
|
||||
}
|
||||
open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> by callback
|
|
@ -569,8 +569,7 @@ internal class CryptoManager @Inject constructor(
|
|||
val reason = String.format(MXCryptoError.UNABLE_TO_ENCRYPT_REASON,
|
||||
algorithm ?: MXCryptoError.NO_MORE_ALGORITHM_REASON)
|
||||
Timber.e("## encryptEventContent() : $reason")
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError(MXCryptoError.UNABLE_TO_ENCRYPT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_ENCRYPT, reason)))
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_ENCRYPT, reason)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -580,10 +579,10 @@ internal class CryptoManager @Inject constructor(
|
|||
*
|
||||
* @param event the raw event.
|
||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||
* @return the MXEventDecryptionResult data, or null in case of error
|
||||
* @return the MXEventDecryptionResult data, or throw in case of error
|
||||
*/
|
||||
@Throws(MXDecryptionException::class)
|
||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult? {
|
||||
@Throws(MXCryptoError::class)
|
||||
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
return runBlocking {
|
||||
internalDecryptEvent(event, timeline).fold(
|
||||
{ throw it },
|
||||
|
@ -599,7 +598,7 @@ internal class CryptoManager @Inject constructor(
|
|||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||
* @param callback the callback to return data or null
|
||||
*/
|
||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult?>) {
|
||||
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
|
||||
GlobalScope.launch(EmptyCoroutineContext) {
|
||||
val result = withContext(coroutineDispatchers.crypto) {
|
||||
internalDecryptEvent(event, timeline)
|
||||
|
@ -616,18 +615,17 @@ internal class CryptoManager @Inject constructor(
|
|||
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try]
|
||||
*/
|
||||
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||
return Try {
|
||||
val eventContent = event.content
|
||||
if (eventContent == null) {
|
||||
Timber.e("## decryptEvent : empty event content")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE))
|
||||
}
|
||||
val eventContent = event.content
|
||||
return if (eventContent == null) {
|
||||
Timber.e("## decryptEvent : empty event content")
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
} else {
|
||||
val algorithm = eventContent["algorithm"]?.toString()
|
||||
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
|
||||
if (alg == null) {
|
||||
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
|
||||
Timber.e("## decryptEvent() : $reason")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE, MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason))
|
||||
} else {
|
||||
alg.decryptEvent(event, timeline)
|
||||
}
|
||||
|
@ -872,7 +870,7 @@ internal class CryptoManager @Inject constructor(
|
|||
/**
|
||||
* Check if the user ids list have some unknown devices.
|
||||
* A success means there is no unknown devices.
|
||||
* If there are some unknown devices, a MXCryptoError.UNKNOWN_DEVICES_CODE exception is triggered.
|
||||
* If there are some unknown devices, a MXCryptoError.UnknownDevice exception is triggered.
|
||||
*
|
||||
* @param userIds the user ids list
|
||||
* @param callback the asynchronous callback.
|
||||
|
@ -890,9 +888,7 @@ internal class CryptoManager @Inject constructor(
|
|||
callback.onSuccess(Unit)
|
||||
} else {
|
||||
// trigger an an unknown devices exception
|
||||
callback.onFailure(
|
||||
Failure.CryptoError(MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
|
||||
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)))
|
||||
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
/*
|
||||
* Copyright 2016 OpenMarket Ltd
|
||||
* Copyright 2017 Vector Creations 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.crypto
|
||||
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
|
||||
/**
|
||||
* This class represents a decryption exception
|
||||
*/
|
||||
class MXDecryptionException
|
||||
(
|
||||
/**
|
||||
* the linked crypto error
|
||||
*/
|
||||
val cryptoError: MXCryptoError?
|
||||
) : Exception() {
|
||||
|
||||
override val message: String?
|
||||
get() = cryptoError?.message ?: super.message
|
||||
|
||||
override fun getLocalizedMessage(): String {
|
||||
return cryptoError?.message ?: super.getLocalizedMessage()
|
||||
}
|
||||
}
|
10
matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt
Normal file → Executable file
10
matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/crypto/MXEventDecryptionResult.kt
Normal file → Executable file
|
@ -1,6 +1,5 @@
|
|||
/*
|
||||
* Copyright 2016 OpenMarket Ltd
|
||||
* Copyright 2017 Vector Creations Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
@ -18,7 +17,6 @@
|
|||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* The result of a (successful) call to decryptEvent.
|
||||
|
@ -28,23 +26,23 @@ data class MXEventDecryptionResult(
|
|||
/**
|
||||
* The plaintext payload for the event (typically containing "type" and "content" fields).
|
||||
*/
|
||||
var clearEvent: JsonDict? = null,
|
||||
val clearEvent: JsonDict,
|
||||
|
||||
/**
|
||||
* Key owned by the sender of this event.
|
||||
* See MXEvent.senderKey.
|
||||
*/
|
||||
var senderCurve25519Key: String? = null,
|
||||
val senderCurve25519Key: String? = null,
|
||||
|
||||
/**
|
||||
* Ed25519 key claimed by the sender of this event.
|
||||
* See MXEvent.claimedEd25519Key.
|
||||
*/
|
||||
var claimedEd25519Key: String? = null,
|
||||
val claimedEd25519Key: String? = null,
|
||||
|
||||
/**
|
||||
* List of curve25519 keys involved in telling us about the senderCurve25519Key and
|
||||
* claimedEd25519Key. See MXEvent.forwardingCurve25519KeyChain.
|
||||
*/
|
||||
var forwardingCurve25519KeyChain: List<String> = ArrayList()
|
||||
val forwardingCurve25519KeyChain: List<String> = emptyList()
|
||||
)
|
||||
|
|
|
@ -18,10 +18,11 @@
|
|||
package im.vector.matrix.android.internal.crypto
|
||||
|
||||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.olm.OlmDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmInboundGroupSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.model.OlmSessionWrapper
|
||||
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
|
||||
|
@ -81,11 +82,6 @@ internal class MXOlmDevice @Inject constructor(
|
|||
// Values are true.
|
||||
private val inboundGroupSessionMessageIndexes: MutableMap<String, MutableMap<String, Boolean>> = HashMap()
|
||||
|
||||
/**
|
||||
* inboundGroupSessionWithId error
|
||||
*/
|
||||
private var inboundGroupSessionWithIdError: MXCryptoError? = null
|
||||
|
||||
init {
|
||||
// Retrieve the account from the store
|
||||
olmAccount = store.getAccount()
|
||||
|
@ -508,22 +504,26 @@ internal class MXOlmDevice @Inject constructor(
|
|||
forwardingCurve25519KeyChain: List<String>,
|
||||
keysClaimed: Map<String, String>,
|
||||
exportFormat: Boolean): Boolean {
|
||||
val existingInboundSession = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
|
||||
|
||||
if (null != existingInboundSession) {
|
||||
// If we already have this session, consider updating it
|
||||
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
getInboundGroupSession(sessionId, senderKey, roomId).fold(
|
||||
{
|
||||
// Nothing to do in case of error
|
||||
},
|
||||
{
|
||||
// If we already have this session, consider updating it
|
||||
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
|
||||
val existingFirstKnown = existingInboundSession.firstKnownIndex!!
|
||||
val newKnownFirstIndex = session.firstKnownIndex!!
|
||||
val existingFirstKnown = it.firstKnownIndex!!
|
||||
val newKnownFirstIndex = session.firstKnownIndex
|
||||
|
||||
//If our existing session is better we keep it
|
||||
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
|
||||
session.olmInboundGroupSession?.releaseSession()
|
||||
return false
|
||||
}
|
||||
}
|
||||
//If our existing session is better we keep it
|
||||
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
|
||||
session.olmInboundGroupSession?.releaseSession()
|
||||
return false
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// sanity check
|
||||
if (null == session.olmInboundGroupSession) {
|
||||
|
@ -594,20 +594,26 @@ internal class MXOlmDevice @Inject constructor(
|
|||
continue
|
||||
}
|
||||
|
||||
val existingOlmSession = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
if (null != existingOlmSession) {
|
||||
// If we already have this session, consider updating it
|
||||
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
.fold(
|
||||
{
|
||||
// Session does not already exist, add it
|
||||
sessions.add(session)
|
||||
},
|
||||
{
|
||||
// If we already have this session, consider updating it
|
||||
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
|
||||
|
||||
// For now we just ignore updates. TODO: implement something here
|
||||
if (existingOlmSession.firstKnownIndex!! <= session.firstKnownIndex!!) {
|
||||
//Ignore this, keep existing
|
||||
session.olmInboundGroupSession!!.releaseSession()
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
sessions.add(session)
|
||||
// For now we just ignore updates. TODO: implement something here
|
||||
if (it.firstKnownIndex!! <= session.firstKnownIndex!!) {
|
||||
//Ignore this, keep existing
|
||||
session.olmInboundGroupSession!!.releaseSession()
|
||||
} else {
|
||||
sessions.add(session)
|
||||
}
|
||||
Unit
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
store.storeInboundGroupSessions(sessions)
|
||||
|
@ -637,81 +643,64 @@ internal class MXOlmDevice @Inject constructor(
|
|||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||
* @return the decrypting result. Nil if the sessionId is unknown.
|
||||
*/
|
||||
@Throws(MXDecryptionException::class)
|
||||
fun decryptGroupMessage(body: String,
|
||||
roomId: String,
|
||||
timeline: String?,
|
||||
sessionId: String,
|
||||
senderKey: String): MXDecryptionResult? {
|
||||
val result = MXDecryptionResult()
|
||||
val session = getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
|
||||
if (null != session) {
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (TextUtils.equals(roomId, session.roomId)) {
|
||||
var errorMessage = ""
|
||||
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
|
||||
try {
|
||||
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||
errorMessage = e.message ?: ""
|
||||
}
|
||||
|
||||
if (null != decryptResult) {
|
||||
if (null != timeline) {
|
||||
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
|
||||
inboundGroupSessionMessageIndexes[timeline] = HashMap()
|
||||
senderKey: String): Try<OlmDecryptionResult> {
|
||||
return getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
.flatMap { session ->
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (roomId == session.roomId) {
|
||||
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
|
||||
try {
|
||||
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
|
||||
} catch (e: OlmException) {
|
||||
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
|
||||
return@flatMap Try.Failure(MXCryptoError.OlmError(e))
|
||||
}
|
||||
|
||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
||||
if (null != timeline) {
|
||||
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
|
||||
inboundGroupSessionMessageIndexes[timeline] = HashMap()
|
||||
}
|
||||
|
||||
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
|
||||
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
||||
Timber.e("## decryptGroupMessage() : $reason")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.DUPLICATED_MESSAGE_INDEX_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
||||
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
|
||||
|
||||
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
|
||||
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
|
||||
Timber.e("## decryptGroupMessage() : $reason")
|
||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
|
||||
}
|
||||
|
||||
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
|
||||
}
|
||||
|
||||
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
|
||||
}
|
||||
store.storeInboundGroupSessions(listOf(session))
|
||||
val payload = try {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
||||
adapter.fromJson(payloadString)
|
||||
} catch (e: Exception) {
|
||||
Timber.e("## decryptGroupMessage() : fails to parse the payload")
|
||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||
}
|
||||
|
||||
store.storeInboundGroupSessions(listOf(session))
|
||||
try {
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
|
||||
val payload = adapter.fromJson(payloadString)
|
||||
result.payload = payload
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## decryptGroupMessage() : RLEncoder.encode failed " + e.message)
|
||||
return null
|
||||
return@flatMap Try.just(
|
||||
OlmDecryptionResult(
|
||||
payload,
|
||||
session.keysClaimed,
|
||||
senderKey,
|
||||
session.forwardingCurve25519KeyChain
|
||||
)
|
||||
)
|
||||
} else {
|
||||
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||
Timber.e("## decryptGroupMessage() : $reason")
|
||||
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
|
||||
}
|
||||
|
||||
if (null == result.payload) {
|
||||
Timber.e("## decryptGroupMessage() : fails to parse the payload")
|
||||
return null
|
||||
}
|
||||
|
||||
result.keysClaimed = session.keysClaimed
|
||||
result.senderKey = senderKey
|
||||
result.forwardingCurve25519KeyChain = session.forwardingCurve25519KeyChain
|
||||
} else {
|
||||
Timber.e("## decryptGroupMessage() : failed to decode the message")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.OLM_ERROR_CODE, errorMessage, null))
|
||||
}
|
||||
} else {
|
||||
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||
Timber.e("## decryptGroupMessage() : $reason")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
||||
}
|
||||
} else {
|
||||
Timber.e("## decryptGroupMessage() : Cannot retrieve inbound group session $sessionId")
|
||||
throw MXDecryptionException(inboundGroupSessionWithIdError)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -725,7 +714,7 @@ internal class MXOlmDevice @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
// Utilities
|
||||
// Utilities
|
||||
|
||||
/**
|
||||
* Verify an ed25519 signature on a JSON object.
|
||||
|
@ -775,27 +764,27 @@ internal class MXOlmDevice @Inject constructor(
|
|||
* @param senderKey the base64-encoded curve25519 key of the sender.
|
||||
* @return the inbound group session.
|
||||
*/
|
||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper? {
|
||||
inboundGroupSessionWithIdError = null
|
||||
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> {
|
||||
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON))
|
||||
}
|
||||
|
||||
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) return null
|
||||
val session = store.getInboundGroupSession(sessionId, senderKey)
|
||||
|
||||
if (null != session) {
|
||||
return if (null != session) {
|
||||
// Check that the room id matches the original one for the session. This stops
|
||||
// the HS pretending a message was targeting a different room.
|
||||
if (!TextUtils.equals(roomId, session.roomId)) {
|
||||
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
|
||||
Timber.e("## getInboundGroupSession() : $errorDescription")
|
||||
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, errorDescription)
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription))
|
||||
} else {
|
||||
Try.just(session)
|
||||
}
|
||||
} else {
|
||||
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
|
||||
inboundGroupSessionWithIdError = MXCryptoError(MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE,
|
||||
MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON, null)
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON))
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -807,6 +796,6 @@ internal class MXOlmDevice @Inject constructor(
|
|||
* @return true if the unbound session keys are known.
|
||||
*/
|
||||
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
|
||||
return null != getInboundGroupSession(sessionId, senderKey, roomId)
|
||||
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,9 +17,9 @@
|
|||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.internal.crypto.IncomingRoomKeyRequest
|
||||
import im.vector.matrix.android.internal.crypto.MXDecryptionException
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||
|
||||
|
@ -33,11 +33,9 @@ internal interface IMXDecrypting {
|
|||
*
|
||||
* @param event the raw event.
|
||||
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
|
||||
* @return the decryption information, or null in case of error
|
||||
* @throws MXDecryptionException the decryption failure reason
|
||||
* @return the decryption information, or an error
|
||||
*/
|
||||
@Throws(MXDecryptionException::class)
|
||||
suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
|
||||
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult>
|
||||
|
||||
/**
|
||||
* Handle a key event.
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package im.vector.matrix.android.internal.crypto.algorithms
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.matrix.android.internal.crypto.*
|
|||
import im.vector.matrix.android.internal.crypto.actions.EnsureOlmSessionsForDevicesAction
|
||||
import im.vector.matrix.android.internal.crypto.actions.MessageEncrypter
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.MXDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.keysbackup.KeysBackup
|
||||
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
|
||||
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
|
||||
|
@ -64,66 +63,71 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||
*/
|
||||
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
|
||||
|
||||
@Throws(MXDecryptionException::class)
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||
return decryptEvent(event, timeline, true)
|
||||
}
|
||||
|
||||
@Throws(MXDecryptionException::class)
|
||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
|
||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()!!
|
||||
if (TextUtils.isEmpty(encryptedEventContent.senderKey) || TextUtils.isEmpty(encryptedEventContent.sessionId) || TextUtils.isEmpty(encryptedEventContent.ciphertext)) {
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_FIELDS_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> {
|
||||
if (event.roomId.isNullOrBlank()) {
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
}
|
||||
|
||||
var cryptoError: MXCryptoError? = null
|
||||
var decryptGroupMessageResult: MXDecryptionResult? = null
|
||||
val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
|
||||
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
|
||||
try {
|
||||
decryptGroupMessageResult = olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext!!, event.roomId!!, timeline, encryptedEventContent.sessionId!!, encryptedEventContent.senderKey!!)
|
||||
} catch (e: MXDecryptionException) {
|
||||
cryptoError = e.cryptoError
|
||||
if (encryptedEventContent.senderKey.isNullOrBlank()
|
||||
|| encryptedEventContent.sessionId.isNullOrBlank()
|
||||
|| encryptedEventContent.ciphertext.isNullOrBlank()) {
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
}
|
||||
// the decryption succeeds
|
||||
if (decryptGroupMessageResult?.payload != null) {
|
||||
val eventDecryptionResult = MXEventDecryptionResult()
|
||||
|
||||
eventDecryptionResult.clearEvent = decryptGroupMessageResult.payload
|
||||
eventDecryptionResult.senderCurve25519Key = decryptGroupMessageResult.senderKey
|
||||
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext, event.roomId, timeline, encryptedEventContent.sessionId, encryptedEventContent.senderKey)
|
||||
.fold(
|
||||
{ throwable ->
|
||||
if (throwable is MXCryptoError.OlmError) {
|
||||
// TODO Check the value of .message
|
||||
if (throwable.olmException.message == "UNKNOWN_MESSAGE_INDEX") {
|
||||
addEventToPendingList(event, timeline)
|
||||
if (requestKeysOnFail) {
|
||||
requestKeysForEvent(event)
|
||||
}
|
||||
}
|
||||
|
||||
if (null != decryptGroupMessageResult.keysClaimed) {
|
||||
eventDecryptionResult.claimedEd25519Key = decryptGroupMessageResult.keysClaimed?.get("ed25519")
|
||||
}
|
||||
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
|
||||
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
|
||||
|
||||
eventDecryptionResult.forwardingCurve25519KeyChain = decryptGroupMessageResult.forwardingCurve25519KeyChain
|
||||
?: emptyList()
|
||||
return eventDecryptionResult
|
||||
} else if (cryptoError != null) {
|
||||
if (cryptoError.isOlmError) {
|
||||
if (MXCryptoError.UNKNOWN_MESSAGE_INDEX == cryptoError.message) {
|
||||
addEventToPendingList(event, timeline)
|
||||
if (requestKeysOnFail) {
|
||||
requestKeysForEvent(event)
|
||||
}
|
||||
}
|
||||
Try.Failure(MXCryptoError.Base(
|
||||
MXCryptoError.ErrorType.OLM,
|
||||
reason,
|
||||
detailedReason))
|
||||
}
|
||||
if (throwable is MXCryptoError.Base) {
|
||||
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
addEventToPendingList(event, timeline)
|
||||
if (requestKeysOnFail) {
|
||||
requestKeysForEvent(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val reason = String.format(MXCryptoError.OLM_REASON, cryptoError.message)
|
||||
val detailedReason = String.format(MXCryptoError.DETAILLED_OLM_REASON, encryptedEventContent.ciphertext, cryptoError.message)
|
||||
|
||||
throw MXDecryptionException(MXCryptoError(
|
||||
MXCryptoError.OLM_ERROR_CODE,
|
||||
reason,
|
||||
detailedReason))
|
||||
} else if (TextUtils.equals(cryptoError.code, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE)) {
|
||||
addEventToPendingList(event, timeline)
|
||||
if (requestKeysOnFail) {
|
||||
requestKeysForEvent(event)
|
||||
}
|
||||
throw MXDecryptionException(cryptoError)
|
||||
}
|
||||
}
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNKNOWN_ERROR_CODE, MXCryptoError.UNKNOWN_ERROR_CODE))
|
||||
Try.Failure(throwable)
|
||||
},
|
||||
{ olmDecryptionResult ->
|
||||
// the decryption succeeds
|
||||
if (olmDecryptionResult.payload != null) {
|
||||
Try.just(
|
||||
MXEventDecryptionResult(
|
||||
clearEvent = olmDecryptionResult.payload,
|
||||
senderCurve25519Key = olmDecryptionResult.senderKey,
|
||||
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
|
||||
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
@ -319,11 +323,20 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||
Try.just(Unit)
|
||||
}
|
||||
Timber.v("""## shareKeysWithDevice() : sharing keys for session ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId""")
|
||||
val inboundGroupSession = olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
|
||||
|
||||
val payloadJson = HashMap<String, Any>()
|
||||
payloadJson["type"] = EventType.FORWARDED_ROOM_KEY
|
||||
payloadJson["content"] = inboundGroupSession!!.exportKeys()!!
|
||||
|
||||
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId)
|
||||
.fold(
|
||||
{
|
||||
// TODO
|
||||
},
|
||||
{
|
||||
// TODO
|
||||
payloadJson["content"] = it.exportKeys() ?: ""
|
||||
}
|
||||
)
|
||||
|
||||
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, Arrays.asList(deviceInfo))
|
||||
val sendToDeviceMap = MXUsersDevicesMap<Any>()
|
||||
|
@ -332,8 +345,6 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
|
|||
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
|
||||
sendToDeviceTask.execute(sendToDeviceParams)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,7 +21,6 @@ package im.vector.matrix.android.internal.crypto.algorithms.megolm
|
|||
import android.text.TextUtils
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Content
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
|
@ -254,29 +253,31 @@ internal class MXMegolmEncryption(
|
|||
/**
|
||||
* process the pending encryptions
|
||||
*/
|
||||
private suspend fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content) = Try<Content> {
|
||||
// Everything is in place, encrypt all pending events
|
||||
val payloadJson = HashMap<String, Any>()
|
||||
payloadJson["room_id"] = roomId
|
||||
payloadJson["type"] = eventType
|
||||
payloadJson["content"] = eventContent
|
||||
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> {
|
||||
return Try<Content> {
|
||||
// Everything is in place, encrypt all pending events
|
||||
val payloadJson = HashMap<String, Any>()
|
||||
payloadJson["room_id"] = roomId
|
||||
payloadJson["type"] = eventType
|
||||
payloadJson["content"] = eventContent
|
||||
|
||||
// Get canonical Json from
|
||||
// Get canonical Json from
|
||||
|
||||
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
|
||||
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
|
||||
val payloadString = convertToUTF8(MoshiProvider.getCanonicalJson(Map::class.java, payloadJson))
|
||||
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
|
||||
|
||||
val map = HashMap<String, Any>()
|
||||
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
|
||||
map["sender_key"] = olmDevice.deviceCurve25519Key!!
|
||||
map["ciphertext"] = ciphertext!!
|
||||
map["session_id"] = session.sessionId
|
||||
val map = HashMap<String, Any>()
|
||||
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
|
||||
map["sender_key"] = olmDevice.deviceCurve25519Key!!
|
||||
map["ciphertext"] = ciphertext!!
|
||||
map["session_id"] = session.sessionId
|
||||
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
map["device_id"] = credentials.deviceId!!
|
||||
session.useCount++
|
||||
map
|
||||
// Include our device ID so that recipients can send us a
|
||||
// m.new_device message if they don't have our session key.
|
||||
map["device_id"] = credentials.deviceId!!
|
||||
session.useCount++
|
||||
map
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -328,9 +329,7 @@ internal class MXMegolmEncryption(
|
|||
if (unknownDevices.isEmpty) {
|
||||
Try.just(devicesInRoom)
|
||||
} else {
|
||||
val cryptoError = MXCryptoError(MXCryptoError.UNKNOWN_DEVICES_CODE,
|
||||
MXCryptoError.UNABLE_TO_ENCRYPT, MXCryptoError.UNKNOWN_DEVICES_REASON, unknownDevices)
|
||||
Try.Failure(Failure.CryptoError(cryptoError))
|
||||
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,13 +17,13 @@
|
|||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||
|
||||
import arrow.core.Try
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.crypto.MXDecryptionException
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.MXOlmDevice
|
||||
import im.vector.matrix.android.internal.crypto.algorithms.IMXDecrypting
|
||||
|
@ -41,30 +41,29 @@ internal class MXOlmDecryption(
|
|||
private val credentials: Credentials)
|
||||
: IMXDecrypting {
|
||||
|
||||
@Throws(MXDecryptionException::class)
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
|
||||
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> {
|
||||
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
|
||||
Timber.e("## decryptEvent() : bad event format")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_EVENT_FORMAT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
|
||||
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON))
|
||||
}
|
||||
|
||||
val cipherText = olmEventContent.ciphertext ?: run {
|
||||
Timber.e("## decryptEvent() : missing cipher text")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_CIPHER_TEXT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
}
|
||||
|
||||
val senderKey = olmEventContent.senderKey ?: run {
|
||||
Timber.e("## decryptEvent() : missing sender key")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_SENDER_KEY_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
|
||||
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON))
|
||||
}
|
||||
|
||||
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
|
||||
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.NOT_INCLUDE_IN_RECIPIENTS_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS,
|
||||
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
|
||||
}
|
||||
|
||||
// The message for myUser
|
||||
|
@ -74,14 +73,14 @@ internal class MXOlmDecryption(
|
|||
|
||||
if (decryptedPayload == null) {
|
||||
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
|
||||
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
}
|
||||
val payloadString = convertFromUTF8(decryptedPayload)
|
||||
if (payloadString == null) {
|
||||
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ENCRYPTED_MESSAGE_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE,
|
||||
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
|
||||
}
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
|
||||
|
@ -89,73 +88,72 @@ internal class MXOlmDecryption(
|
|||
|
||||
if (payload == null) {
|
||||
Timber.e("## decryptEvent failed : null payload")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
}
|
||||
|
||||
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
|
||||
Timber.e("## decryptEvent() : bad olmPayloadContent format")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_DECRYPTED_FORMAT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT,
|
||||
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.recipient.isNullOrBlank()) {
|
||||
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
|
||||
Timber.e("## decryptEvent() : $reason")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, reason))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
reason))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.recipient != credentials.userId) {
|
||||
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
|
||||
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient)))
|
||||
}
|
||||
|
||||
val recipientKeys = olmPayloadContent.recipient_keys ?: run {
|
||||
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys")))
|
||||
}
|
||||
|
||||
val ed25519 = recipientKeys["ed25519"]
|
||||
|
||||
if (ed25519 != olmDevice.deviceEd25519Key) {
|
||||
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_RECIPIENT_KEY_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.BAD_RECIPIENT_KEY_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
|
||||
MXCryptoError.BAD_RECIPIENT_KEY_REASON))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.sender.isNullOrBlank()) {
|
||||
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.MISSING_PROPERTY_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
|
||||
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender")))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.sender != event.senderId) {
|
||||
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.FORWARDED_MESSAGE_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
|
||||
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender)))
|
||||
}
|
||||
|
||||
if (olmPayloadContent.room_id != event.roomId) {
|
||||
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.BAD_ROOM_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
|
||||
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id)))
|
||||
}
|
||||
|
||||
val keys = olmPayloadContent.keys ?: run {
|
||||
Timber.e("## decryptEvent failed : null keys")
|
||||
throw MXDecryptionException(MXCryptoError(MXCryptoError.UNABLE_TO_DECRYPT_ERROR_CODE,
|
||||
MXCryptoError.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
|
||||
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
|
||||
}
|
||||
|
||||
val result = MXEventDecryptionResult()
|
||||
result.clearEvent = payload
|
||||
result.senderCurve25519Key = senderKey
|
||||
result.claimedEd25519Key = keys["ed25519"]
|
||||
|
||||
return result
|
||||
return Try.just(MXEventDecryptionResult(
|
||||
clearEvent = payload,
|
||||
senderCurve25519Key = senderKey,
|
||||
claimedEd25519Key = keys["ed25519"]
|
||||
))
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -14,32 +14,32 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package im.vector.matrix.android.internal.crypto.algorithms
|
||||
package im.vector.matrix.android.internal.crypto.algorithms.olm
|
||||
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
|
||||
/**
|
||||
* This class represents the decryption result.
|
||||
*/
|
||||
data class MXDecryptionResult(
|
||||
data class OlmDecryptionResult(
|
||||
/**
|
||||
* The decrypted payload (with properties 'type', 'content')
|
||||
*/
|
||||
var payload: JsonDict? = null,
|
||||
val payload: JsonDict? = null,
|
||||
|
||||
/**
|
||||
* keys that the sender of the event claims ownership of:
|
||||
* map from key type to base64-encoded key.
|
||||
*/
|
||||
var keysClaimed: Map<String, String>? = null,
|
||||
val keysClaimed: Map<String, String>? = null,
|
||||
|
||||
/**
|
||||
* The curve25519 key that the sender of the event is known to have ownership of.
|
||||
*/
|
||||
var senderKey: String? = null,
|
||||
val senderKey: String? = null,
|
||||
|
||||
/**
|
||||
* Devices which forwarded this session to us (normally empty).
|
||||
*/
|
||||
var forwardingCurve25519KeyChain: List<String>? = null
|
||||
)
|
||||
val forwardingCurve25519KeyChain: List<String>? = null
|
||||
)
|
|
@ -20,7 +20,7 @@ import im.vector.matrix.android.api.session.events.model.Content
|
|||
|
||||
data class MXEncryptEventContentResult(
|
||||
/**
|
||||
* The event content
|
||||
* The encrypted event content
|
||||
*/
|
||||
val eventContent: Content,
|
||||
/**
|
||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.crypto.store.db.query.delete
|
|||
import im.vector.matrix.android.internal.crypto.store.db.query.getById
|
||||
import im.vector.matrix.android.internal.crypto.store.db.query.getOrCreate
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import io.realm.Realm
|
||||
import io.realm.RealmConfiguration
|
||||
import io.realm.Sort
|
||||
import io.realm.kotlin.where
|
||||
|
@ -47,6 +48,9 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||
* Memory cache, to correctly release JNI objects
|
||||
* ========================================================================================== */
|
||||
|
||||
// A realm instance, for faster future getInstance. Do not use it
|
||||
private var realmLocker: Realm? = null
|
||||
|
||||
// The olm account
|
||||
private var olmAccount: OlmAccount? = null
|
||||
|
||||
|
@ -86,6 +90,8 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||
}
|
||||
|
||||
override fun open() {
|
||||
realmLocker = Realm.getInstance(realmConfiguration)
|
||||
|
||||
// Ensure CryptoMetadataEntity is inserted in DB
|
||||
doWithRealm(realmConfiguration) { realm ->
|
||||
var currentMetadata = realm.where<CryptoMetadataEntity>().findFirst()
|
||||
|
@ -131,6 +137,9 @@ internal class RealmCryptoStore(private val realmConfiguration: RealmConfigurati
|
|||
inboundGroupSessionToRelease.clear()
|
||||
|
||||
olmAccount?.releaseAccount()
|
||||
|
||||
realmLocker?.close()
|
||||
realmLocker = null
|
||||
}
|
||||
|
||||
override fun storeDeviceId(deviceId: String) {
|
||||
|
|
|
@ -26,7 +26,6 @@ import im.vector.matrix.android.api.session.events.model.Event
|
|||
import im.vector.matrix.android.api.session.room.send.SendService
|
||||
import im.vector.matrix.android.api.util.Cancelable
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.addTo
|
||||
import im.vector.matrix.android.internal.session.content.UploadContentWorker
|
||||
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
|
||||
import im.vector.matrix.android.internal.util.CancelableWork
|
||||
|
@ -82,11 +81,9 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
|
|||
}
|
||||
|
||||
override fun sendMedias(attachments: List<ContentAttachmentData>): Cancelable {
|
||||
val cancelableBag = CancelableBag()
|
||||
attachments.forEach {
|
||||
sendMedia(it).addTo(cancelableBag)
|
||||
return attachments.mapTo(CancelableBag()) {
|
||||
sendMedia(it)
|
||||
}
|
||||
return cancelableBag
|
||||
}
|
||||
|
||||
override fun redactEvent(event: Event, reason: String?): Cancelable {
|
||||
|
|
|
@ -23,7 +23,6 @@ import im.vector.matrix.android.api.session.events.model.toModel
|
|||
import im.vector.matrix.android.api.session.room.timeline.Timeline
|
||||
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
|
||||
import im.vector.matrix.android.api.util.CancelableBag
|
||||
import im.vector.matrix.android.api.util.addTo
|
||||
import im.vector.matrix.android.internal.crypto.NewSessionListener
|
||||
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
|
||||
import im.vector.matrix.android.internal.database.mapper.asDomain
|
||||
|
@ -393,7 +392,7 @@ internal class DefaultTimeline(
|
|||
limit = limit)
|
||||
|
||||
Timber.v("Should fetch $limit items $direction")
|
||||
paginationTask.configureWith(params)
|
||||
cancelableBag += paginationTask.configureWith(params)
|
||||
.enableRetry()
|
||||
.dispatchTo(object : MatrixCallback<TokenChunkEventPersistor.Result> {
|
||||
override fun onSuccess(data: TokenChunkEventPersistor.Result) {
|
||||
|
@ -412,7 +411,6 @@ internal class DefaultTimeline(
|
|||
}
|
||||
})
|
||||
.executeBy(taskExecutor)
|
||||
.addTo(cancelableBag)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -17,15 +17,14 @@
|
|||
package im.vector.matrix.android.internal.session.sync
|
||||
|
||||
import android.text.TextUtils
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.api.session.events.model.Event
|
||||
import im.vector.matrix.android.api.session.events.model.EventType
|
||||
import im.vector.matrix.android.api.session.events.model.toModel
|
||||
import im.vector.matrix.android.api.session.room.model.message.MessageContent
|
||||
import im.vector.matrix.android.internal.crypto.CryptoManager
|
||||
import im.vector.matrix.android.internal.crypto.MXDecryptionException
|
||||
import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
|
||||
import im.vector.matrix.android.internal.crypto.verification.DefaultSasVerificationService
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
|
||||
import im.vector.matrix.android.internal.session.sync.model.ToDeviceSyncResponse
|
||||
import timber.log.Timber
|
||||
|
@ -40,7 +39,7 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
|
|||
// Decrypt event if necessary
|
||||
decryptEvent(event, null)
|
||||
if (TextUtils.equals(event.getClearType(), EventType.MESSAGE)
|
||||
&& event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
|
||||
&& event.mClearEvent?.content?.toModel<MessageContent>()?.type == "m.bad.encrypted") {
|
||||
Timber.e("## handleToDeviceEvent() : Warning: Unable to decrypt to-device event : " + event.content)
|
||||
} else {
|
||||
sasVerificationService.onToDeviceEvent(event)
|
||||
|
@ -66,8 +65,8 @@ internal class CryptoSyncHandler @Inject constructor(private val cryptoManager:
|
|||
var result: MXEventDecryptionResult? = null
|
||||
try {
|
||||
result = cryptoManager.decryptEvent(event, timelineId ?: "")
|
||||
} catch (exception: MXDecryptionException) {
|
||||
event.setCryptoError(exception.cryptoError)
|
||||
} catch (exception: MXCryptoError) {
|
||||
event.setCryptoError(exception)
|
||||
}
|
||||
|
||||
if (null != result) {
|
||||
|
|
|
@ -25,19 +25,13 @@ import timber.log.Timber
|
|||
* @return the utf-8 string
|
||||
*/
|
||||
fun convertToUTF8(s: String): String? {
|
||||
var out: String? = s
|
||||
|
||||
if (null != out) {
|
||||
try {
|
||||
val bytes = out.toByteArray(charset("UTF-8"))
|
||||
out = String(bytes)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## convertToUTF8() failed")
|
||||
}
|
||||
|
||||
return try {
|
||||
val bytes = s.toByteArray(Charsets.UTF_8)
|
||||
String(bytes)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## convertToUTF8() failed")
|
||||
null
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -47,17 +41,11 @@ fun convertToUTF8(s: String): String? {
|
|||
* @return the utf-16 string
|
||||
*/
|
||||
fun convertFromUTF8(s: String): String? {
|
||||
var out: String? = s
|
||||
|
||||
if (null != out) {
|
||||
try {
|
||||
val bytes = out.toByteArray()
|
||||
out = String(bytes, charset("UTF-8"))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## convertFromUTF8() failed")
|
||||
}
|
||||
|
||||
return try {
|
||||
val bytes = s.toByteArray()
|
||||
String(bytes, Charsets.UTF_8)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## convertFromUTF8() failed")
|
||||
null
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ class EmojiCompatFontProvider @Inject constructor(): FontsContractCompat.FontReq
|
|||
}
|
||||
|
||||
fun addListener(listener: FontProviderListener) {
|
||||
if (!listeners.contains(listener)) {
|
||||
if (listener !in listeners) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,14 +26,16 @@ import im.vector.riotx.R
|
|||
internal abstract class DialogAdapter(context: Context) : ArrayAdapter<DialogListItem>(context, R.layout.item_dialog) {
|
||||
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
|
||||
var view = convertView
|
||||
if (view == null) {
|
||||
val view: View
|
||||
if (convertView == null) {
|
||||
view = LayoutInflater.from(context).inflate(R.layout.item_dialog, parent, false)
|
||||
view.tag = DialogListItemHolder(view)
|
||||
} else {
|
||||
view = convertView
|
||||
}
|
||||
(view!!.tag as DialogListItemHolder).let {
|
||||
it.icon.setImageResource(getItem(position).iconRes)
|
||||
it.text.setText(getItem(position).titleRes)
|
||||
with(view.tag as DialogListItemHolder) {
|
||||
icon.setImageResource(getItem(position).iconRes)
|
||||
text.setText(getItem(position).titleRes)
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
|
|
@ -22,12 +22,7 @@ import android.os.Bundle
|
|||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.MenuRes
|
||||
import androidx.annotation.Nullable
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.annotation.*
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -260,8 +255,7 @@ abstract class VectorBaseActivity : BaseMvRxActivity(), HasScreenInjector {
|
|||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
if (item.itemId == android.R.id.home) {
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
onBackPressed()
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.home
|
|||
|
||||
import androidx.annotation.ColorRes
|
||||
import im.vector.riotx.R
|
||||
import kotlin.math.abs
|
||||
|
||||
|
||||
@ColorRes
|
||||
|
@ -36,7 +37,7 @@ fun getColorFromUserId(userId: String?): Int {
|
|||
i++
|
||||
}
|
||||
|
||||
return when (Math.abs(hash) % 8 + 1) {
|
||||
return when (abs(hash) % 8 + 1) {
|
||||
1 -> R.color.riotx_username_1
|
||||
2 -> R.color.riotx_username_2
|
||||
3 -> R.color.riotx_username_3
|
||||
|
|
|
@ -28,7 +28,6 @@ import im.vector.riotx.core.utils.DebouncedClickListener
|
|||
import im.vector.riotx.features.home.AvatarRenderer
|
||||
import im.vector.riotx.features.home.room.detail.timeline.TimelineEventController
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.MessageTextItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.item.NoticeItem_
|
||||
import im.vector.riotx.features.home.room.detail.timeline.util.MessageInformationDataFactory
|
||||
import me.gujun.android.span.span
|
||||
import javax.inject.Inject
|
||||
|
@ -49,10 +48,16 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
|||
EventType.ENCRYPTED == event.root.getClearType() -> {
|
||||
val cryptoError = event.root.mCryptoError
|
||||
val errorDescription =
|
||||
if (cryptoError?.code == MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_ERROR_CODE) {
|
||||
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
|
||||
if (cryptoError is MXCryptoError.Base) {
|
||||
if (cryptoError.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
|
||||
stringProvider.getString(R.string.notice_crypto_error_unkwown_inbound_session_id)
|
||||
} else {
|
||||
// TODO i18n
|
||||
cryptoError.technicalMessage
|
||||
}
|
||||
} else {
|
||||
cryptoError?.message
|
||||
// Cannot happen (for now)
|
||||
"Other error"
|
||||
}
|
||||
|
||||
val message = stringProvider.getString(R.string.notice_crypto_unable_to_decrypt, errorDescription)
|
||||
|
@ -77,7 +82,7 @@ class EncryptedItemFactory @Inject constructor(private val messageInformationDat
|
|||
}))
|
||||
.longClickListener { view ->
|
||||
return@longClickListener callback?.onEventLongClicked(informationData, null, view)
|
||||
?: false
|
||||
?: false
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
|
|
|
@ -83,7 +83,7 @@ class TimelineItemFactory @Inject constructor(private val messageItemFactory: Me
|
|||
NoticeItem_()
|
||||
.avatarRenderer(avatarRenderer)
|
||||
.informationData(informationData)
|
||||
.noticeText("{ \"type\": ${event.root.type} }")
|
||||
.noticeText("{ \"type\": ${event.root.getClearType()} }")
|
||||
.highlighted(highlight)
|
||||
.baseCallback(callback)
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import im.vector.riotx.core.di.ActiveSessionHolder
|
|||
import im.vector.riotx.core.glide.GlideApp
|
||||
import im.vector.riotx.core.utils.DimensionUtils.dpToPx
|
||||
import kotlinx.android.parcel.Parcelize
|
||||
import timber.log.Timber
|
||||
import java.io.File
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -94,6 +95,11 @@ class ImageContentRenderer @Inject constructor(private val activeSessionHolder:
|
|||
val fullSize = contentUrlResolver.resolveFullSize(data.url)
|
||||
val thumbnail = contentUrlResolver.resolveThumbnail(data.url, width, height, ContentUrlResolver.ThumbnailMethod.SCALE)
|
||||
|
||||
if (fullSize.isNullOrBlank() || thumbnail.isNullOrBlank()) {
|
||||
Timber.w("Invalid urls")
|
||||
return
|
||||
}
|
||||
|
||||
// TODO DECRYPT_FILE Decrypt file
|
||||
imageView.showImage(
|
||||
Uri.parse(thumbnail),
|
||||
|
|
Loading…
Reference in a new issue