Merge remote-tracking branch 'upstream/dev' into open_groups_V2

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java
#	app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java
#	app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/protocol/MultiDeviceProtocol.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateSelectionView.kt
#	app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt
#	libsession/src/main/java/org/session/libsession/messaging/mentions/MentionsManager.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt
#	libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/ClosedGroupPoller.kt
#	libsession/src/main/java/org/session/libsession/messaging/utilities/DotNetAPI.kt
#	libsession/src/main/java/org/session/libsession/utilities/mentions/Mention.kt
#	libsignal/src/main/java/org/session/libsignal/service/loki/Mention.kt
#	libsignal/src/main/java/org/session/libsignal/service/loki/utilities/mentions/Mention.kt
This commit is contained in:
jubb
2021-05-03 15:37:53 +10:00
67 changed files with 319 additions and 286 deletions

View File

@@ -110,8 +110,10 @@ interface StorageProtocol {
fun createGroup(groupID: String, title: String?, members: List<Address>, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List<Address>, formationTimestamp: Long)
fun isGroupActive(groupPublicKey: String): Boolean
fun setActive(groupID: String, value: Boolean)
fun getZombieMember(groupID: String): Set<String>
fun removeMember(groupID: String, member: Address)
fun updateMembers(groupID: String, members: List<Address>)
fun updateZombieMembers(groupID: String, members: List<Address>)
// Closed Group
fun getAllClosedGroupPublicKeys(): Set<String>
fun getAllActiveClosedGroupPublicKeys(): Set<String>

View File

@@ -8,7 +8,7 @@ import org.session.libsession.snode.OnionRequestAPI
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.*
import java.net.URL

View File

@@ -15,7 +15,7 @@ import org.session.libsignal.service.internal.crypto.PaddingInputStream
import org.session.libsignal.service.internal.push.PushAttachmentData
import org.session.libsignal.service.internal.push.http.AttachmentCipherOutputStreamFactory
import org.session.libsignal.service.internal.util.Util
import org.session.libsignal.service.loki.utilities.PlaintextOutputStreamFactory
import org.session.libsignal.service.loki.PlaintextOutputStreamFactory
import org.session.libsignal.utilities.logging.Log
class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val message: Message, val messageSendJobID: String) : Job {

View File

@@ -1,9 +1,9 @@
package org.session.libsession.messaging.mentions
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.utilities.mentions.Mention
import org.session.libsignal.service.loki.Mention
import org.session.libsignal.service.loki.database.LokiUserDatabaseProtocol
import org.session.libsignal.service.loki.LokiUserDatabaseProtocol
class MentionsManager(private val userPublicKey: String, private val userDatabase: LokiUserDatabaseProtocol) {
var userPublicKeyCache = mutableMapOf<Long, Set<String>>() // Thread ID to set of user hex encoded public keys

View File

@@ -145,7 +145,7 @@ class ClosedGroupControlMessage() : ControlMessage() {
}
is Kind.EncryptionKeyPair -> {
closedGroupControlMessage.type = DataMessage.ClosedGroupControlMessage.Type.ENCRYPTION_KEY_PAIR
closedGroupControlMessage.publicKey = kind.publicKey
closedGroupControlMessage.publicKey = kind.publicKey ?: ByteString.EMPTY
closedGroupControlMessage.addAllWrappers(kind.wrappers.map { it.toProto() })
}
is Kind.NameChange -> {

View File

@@ -21,7 +21,6 @@ import java.util.*
object OpenGroupAPI: DotNetAPI() {
private val moderators: HashMap<String, HashMap<Long, Set<String>>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
val sharedContext = Kovenant.createContext()
// region Settings
private val fallbackBatchCount = 64
@@ -61,7 +60,7 @@ object OpenGroupAPI: DotNetAPI() {
parameters["count"] = fallbackBatchCount
parameters["include_deleted"] = 0
}
return execute(HTTPVerb.GET, server, "channels/$channel/messages", parameters = parameters).then(sharedContext) { json ->
return execute(HTTPVerb.GET, server, "channels/$channel/messages", parameters = parameters).then { json ->
try {
val data = json["data"] as List<Map<*, *>>
val messages = data.mapNotNull { message ->
@@ -166,7 +165,7 @@ object OpenGroupAPI: DotNetAPI() {
} else {
parameters["count"] = fallbackBatchCount
}
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/deletes", parameters = parameters).then(sharedContext) { json ->
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/deletes", parameters = parameters).then { json ->
try {
val deletedMessageServerIDs = (json["data"] as List<Map<*, *>>).mapNotNull { deletion ->
try {
@@ -202,7 +201,7 @@ object OpenGroupAPI: DotNetAPI() {
retryIfNeeded(maxRetryCount) {
Log.d("Loki", "Sending message to open group with ID: $channel on server: $server.")
val parameters = signedMessage.toJSON()
execute(HTTPVerb.POST, server, "channels/$channel/messages", parameters = parameters).then(sharedContext) { json ->
execute(HTTPVerb.POST, server, "channels/$channel/messages", parameters = parameters).then { json ->
try {
val data = json["data"] as Map<*, *>
val serverID = (data["id"] as? Long) ?: (data["id"] as? Int)?.toLong() ?: (data["id"] as String).toLong()
@@ -255,7 +254,7 @@ object OpenGroupAPI: DotNetAPI() {
@JvmStatic
fun getModerators(channel: Long, server: String): Promise<Set<String>, Exception> {
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then(sharedContext) { json ->
return execute(HTTPVerb.GET, server, "loki/v1/channel/$channel/get_moderators").then { json ->
try {
@Suppress("UNCHECKED_CAST") val moderators = json["moderators"] as? List<String>
val moderatorsAsSet = moderators.orEmpty().toSet()
@@ -276,7 +275,7 @@ object OpenGroupAPI: DotNetAPI() {
fun getChannelInfo(channel: Long, server: String): Promise<OpenGroupInfo, Exception> {
return retryIfNeeded(maxRetryCount) {
val parameters = mapOf( "include_annotations" to 1 )
execute(HTTPVerb.GET, server, "/channels/$channel", parameters = parameters).then(sharedContext) { json ->
execute(HTTPVerb.GET, server, "/channels/$channel", parameters = parameters).then { json ->
try {
val data = json["data"] as Map<*, *>
val annotations = data["annotations"] as List<Map<*, *>>
@@ -357,7 +356,7 @@ object OpenGroupAPI: DotNetAPI() {
@JvmStatic
fun getDisplayNames(publicKeys: Set<String>, server: String): Promise<Map<String, String>, Exception> {
return getUserProfiles(publicKeys, server, false).map(sharedContext) { json ->
return getUserProfiles(publicKeys, server, false).map { json ->
val mapping = mutableMapOf<String, String>()
for (user in json) {
if (user["username"] != null) {

View File

@@ -17,16 +17,13 @@ import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2.Error
import org.session.libsession.snode.OnionRequestAPI
import org.session.libsession.utilities.AESGCM
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsignal.service.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.api.utilities.HTTP.Verb.*
import org.session.libsignal.service.loki.HTTP
import org.session.libsignal.service.loki.HTTP.Verb.*
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.toHexString
import org.session.libsignal.utilities.Base64.*
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.utilities.createContext
import org.session.libsignal.utilities.logging.Log
import org.whispersystems.curve25519.Curve25519
import java.util.*
@@ -39,7 +36,6 @@ object OpenGroupAPIV2 {
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
private val sharedContext = Kovenant.createContext()
private val curve = Curve25519.getInstance(Curve25519.BEST)
sealed class Error : Exception() {
@@ -128,8 +124,8 @@ object OpenGroupAPIV2 {
}
when (request.verb) {
GET -> requestBuilder.get()
PUT -> requestBuilder.put(createBody(request.parameters))
POST -> requestBuilder.post(createBody(request.parameters))
PUT -> requestBuilder.put(createBody(request.parameters)!!)
POST -> requestBuilder.post(createBody(request.parameters)!!)
DELETE -> requestBuilder.delete(createBody(request.parameters))
}
@@ -156,7 +152,7 @@ object OpenGroupAPIV2 {
}
}
return if (request.isAuthRequired) {
getAuthToken(request.room!!, request.server).bind(sharedContext) { execute(it) }
getAuthToken(request.room!!, request.server).bind { execute(it) }
} else {
execute(null)
}
@@ -164,7 +160,7 @@ object OpenGroupAPIV2 {
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val result = json["result"] as? String ?: throw Error.PARSING_FAILED
decode(result)
}
@@ -176,7 +172,7 @@ object OpenGroupAPIV2 {
Promise.of(it)
} ?: run {
requestNewAuthToken(room, server)
.bind(sharedContext) { claimAuthToken(it, room, server) }
.bind { claimAuthToken(it, room, server) }
.success { authToken ->
storage.setAuthToken(room, server, authToken)
}
@@ -188,7 +184,7 @@ object OpenGroupAPIV2 {
?: return Promise.ofFail(Error.GENERIC)
val queryParameters = mutableMapOf("public_key" to publicKey)
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.PARSING_FAILED
val base64EncodedCiphertext = challenge["ciphertext"] as? String
?: throw Error.PARSING_FAILED
@@ -211,12 +207,12 @@ object OpenGroupAPIV2 {
val headers = mapOf("Authorization" to authToken)
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
parameters = parameters, headers = headers, isAuthRequired = false)
return send(request).map(sharedContext) { authToken }
return send(request).map { authToken }
}
fun deleteAuthToken(room: String, server: String): Promise<Unit, Exception> {
val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token")
return send(request).map(sharedContext) {
return send(request).map {
MessagingModuleConfiguration.shared.storage.removeAuthToken(room, server)
}
}
@@ -226,14 +222,14 @@ object OpenGroupAPIV2 {
val base64EncodedFile = encodeBytes(file)
val parameters = mapOf("file" to base64EncodedFile)
val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
json["result"] as? Long ?: throw Error.PARSING_FAILED
}
}
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED
decode(base64EncodedFile) ?: throw Error.PARSING_FAILED
}
@@ -243,7 +239,7 @@ object OpenGroupAPIV2 {
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
val jsonMessage = signedMessage.toJSON()
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
?: throw Error.PARSING_FAILED
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
@@ -259,7 +255,7 @@ object OpenGroupAPIV2 {
queryParameters += "from_server_id" to lastId.toString()
}
val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters)
return send(request).map(sharedContext) { jsonList ->
return send(request).map { jsonList ->
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String, Any>>
?: throw Error.PARSING_FAILED
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
@@ -296,7 +292,7 @@ object OpenGroupAPIV2 {
@JvmStatic
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
val request = Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
return send(request).map(sharedContext) {
return send(request).map {
Log.d("Loki", "Deleted server message")
}
}
@@ -308,7 +304,7 @@ object OpenGroupAPIV2 {
queryParameters["from_server_id"] = last.toString()
}
val request = Request(verb = GET, room = room, server = server, endpoint = "deleted_messages", queryParameters = queryParameters)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
val idsAsString = JsonUtil.toJson(json["ids"])
val serverIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.PARSING_FAILED
@@ -329,7 +325,7 @@ object OpenGroupAPIV2 {
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String>
?: throw Error.PARSING_FAILED
val id = "$server.$room"
@@ -342,14 +338,14 @@ object OpenGroupAPIV2 {
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
val parameters = mapOf("public_key" to publicKey)
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
return send(request).map(sharedContext) {
return send(request).map {
Log.d("Loki", "Banned user $publicKey from $server.$room")
}
}
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
return send(request).map(sharedContext) {
return send(request).map {
Log.d("Loki", "Unbanned user $publicKey from $server.$room")
}
}
@@ -380,7 +376,7 @@ object OpenGroupAPIV2 {
}
val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false, parameters = mapOf("requests" to requests))
// build a request for all rooms
return send(request = request).map(sharedContext) { json ->
return send(request = request).map { json ->
val results = json["results"] as? List<*> ?: throw Error.PARSING_FAILED
results.mapNotNull { roomJson ->
@@ -462,7 +458,7 @@ object OpenGroupAPIV2 {
fun getInfo(room: String, server: String): Promise<Info, Exception> {
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms/$room", isAuthRequired = false)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val rawRoom = json["room"] as? Map<*, *> ?: throw Error.PARSING_FAILED
val id = rawRoom["id"] as? String ?: throw Error.PARSING_FAILED
val name = rawRoom["name"] as? String ?: throw Error.PARSING_FAILED
@@ -473,7 +469,7 @@ object OpenGroupAPIV2 {
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val rawRooms = json["rooms"] as? List<Map<*, *>> ?: throw Error.PARSING_FAILED
rawRooms.mapNotNull {
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
@@ -487,7 +483,7 @@ object OpenGroupAPIV2 {
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
return send(request).map(sharedContext) { json ->
return send(request).map { json ->
val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED
val storage = MessagingModuleConfiguration.shared.storage
storage.setUserCount(room, server, memberCount)

View File

@@ -169,15 +169,24 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
Log.d("Loki", "Invalid closed group update.")
throw Error.InvalidClosedGroupUpdate
}
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
// Save the new group members
storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
val admins = group.admins.map { it.serialize() }
if (!admins.contains(userPublicKey)) {
Log.d("Loki", "Only an admin can remove members from a group.")
throw Error.InvalidClosedGroupUpdate
}
val updatedMembers = group.members.map { it.serialize() }.toSet() - membersToRemove
if (membersToRemove.any { it in admins } && updatedMembers.isNotEmpty()) {
Log.d("Loki", "Can't remove admin from closed group unless the group is destroyed entirely.")
throw Error.InvalidClosedGroupUpdate
}
// Save the new group members
storage.updateMembers(groupID, updatedMembers.map { Address.fromSerialized(it) })
// Update the zombie list
val oldZombies = storage.getZombieMember(groupID)
storage.updateZombieMembers(groupID, oldZombies.minus(membersToRemove).map { Address.fromSerialized(it) })
val removeMembersAsData = membersToRemove.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) }
val name = group.title
// Send the update to the group
val memberUpdateKind = ClosedGroupControlMessage.Kind.MembersRemoved(removeMembersAsData)
@@ -185,14 +194,21 @@ fun MessageSender.removeMembers(groupPublicKey: String, membersToRemove: List<St
val closedGroupControlMessage = ClosedGroupControlMessage(memberUpdateKind)
closedGroupControlMessage.sentTimestamp = sentTime
send(closedGroupControlMessage, Address.fromSerialized(groupID))
val isCurrentUserAdmin = admins.contains(userPublicKey)
if (isCurrentUserAdmin) {
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
}
// Send the new encryption key pair to the remaining group members
// At this stage we know the user is admin, no need to test
generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMembers)
// Notify the user
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, membersToRemove, admins, threadID, sentTime)
// Insert an outgoing notification
// we don't display zombie members in the notification as users have already been notified when those members left
val notificationMembers = membersToRemove.minus(oldZombies)
if (notificationMembers.isNotEmpty()) {
// no notification to display when only zombies have been removed
val infoType = SignalServiceGroup.Type.MEMBER_REMOVED
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, infoType, name, notificationMembers, admins, threadID, sentTime)
}
}
fun MessageSender.leave(groupPublicKey: String, notifyUser: Boolean = true): Promise<Unit, Exception> {

View File

@@ -298,7 +298,8 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
val storage = MessagingModuleConfiguration.shared.storage
val senderPublicKey = message.sender ?: return
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.EncryptionKeyPair ?: return
val groupPublicKey = kind.publicKey?.toByteArray()?.toHexString() ?: message.groupPublicKey ?: return
var groupPublicKey = kind.publicKey?.toByteArray()?.toHexString()
if (groupPublicKey.isNullOrEmpty()) groupPublicKey = message.groupPublicKey ?: return
val userPublicKey = storage.getUserPublicKey()!!
val userKeyPair = storage.getUserX25519KeyPair()
// Unwrap the message
@@ -312,7 +313,7 @@ private fun MessageReceiver.handleClosedGroupEncryptionKeyPair(message: ClosedGr
return
}
if (!group.admins.map { it.toString() }.contains(senderPublicKey)) {
Log.d("Loki", "Ignoring closed group encryption key pair from non-member.")
Log.d("Loki", "Ignoring closed group encryption key pair from non-admin.")
return
}
// Find our wrapper and decrypt it if possible
@@ -393,6 +394,7 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
val newMembers = members + updateMembers
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
// Notify the user
if (userPublicKey == senderPublicKey) {
// sender is a linked device
@@ -415,6 +417,11 @@ private fun MessageReceiver.handleClosedGroupMembersAdded(message: ClosedGroupCo
}
}
/// Removes the given members from the group IF
/// • it wasn't the admin that was removed (that should happen through a `MEMBER_LEFT` message).
/// • the admin sent the message (only the admin can truly remove members).
/// If we're among the users that were removed, delete all encryption key pairs and the group public key, unsubscribe
/// from push notifications for this closed group, and remove the given members from the zombie list for this group.
private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroupControlMessage) {
val context = MessagingModuleConfiguration.shared.context
val storage = MessagingModuleConfiguration.shared.storage
@@ -428,7 +435,7 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
return
}
if (!group.isActive) {
Log.d("Loki", "Ignoring closed group info message for inactive group")
Log.d("Loki", "Ignoring closed group info message for inactive group.")
return
}
val name = group.title
@@ -439,6 +446,18 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
// Users that are part of this remove update
val updateMembers = kind.members.map { it.toByteArray().toHexString() }
// Check that the admin wasn't removed
if (updateMembers.contains(admins.first())) {
Log.d("Loki", "Ignoring invalid closed group update.")
return
}
// Check that the message was sent by the group admin
if (!admins.contains(senderPublicKey)) {
Log.d("Loki", "Ignoring invalid closed group update.")
return
}
if (!isValidGroupUpdate(group, message.sentTimestamp!!, senderPublicKey)) { return }
// If admin leaves the group is disbanded
val didAdminLeave = admins.any { it in updateMembers }
@@ -455,25 +474,34 @@ private fun MessageReceiver.handleClosedGroupMembersRemoved(message: ClosedGroup
if (didAdminLeave || wasCurrentUserRemoved) {
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
} else {
val isCurrentUserAdmin = admins.contains(userPublicKey)
storage.updateMembers(groupID, newMembers.map { Address.fromSerialized(it) })
if (isCurrentUserAdmin) {
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, newMembers)
}
}
// update zombie members
val zombies = storage.getZombieMember(groupID)
storage.updateZombieMembers(groupID, zombies.minus(updateMembers).map { Address.fromSerialized(it) })
val type = if (senderLeft) SignalServiceGroup.Type.QUIT
else SignalServiceGroup.Type.MEMBER_REMOVED
// Notify the user
if (userPublicKey == senderPublicKey) {
// sender is a linked device
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, type, name, updateMembers, admins, threadID, message.sentTimestamp!!)
} else {
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, updateMembers, admins, message.sentTimestamp!!)
// we don't display zombie members in the notification as users have already been notified when those members left
val notificationMembers = updateMembers.minus(zombies)
if (notificationMembers.isNotEmpty()) {
// no notification to display when only zombies have been removed
if (userPublicKey == senderPublicKey) {
// sender is a linked device
val threadID = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupID))
storage.insertOutgoingInfoMessage(context, groupID, type, name, notificationMembers, admins, threadID, message.sentTimestamp!!)
} else {
storage.insertIncomingInfoMessage(context, senderPublicKey, groupID, type, name, notificationMembers, admins, message.sentTimestamp!!)
}
}
}
/// If a regular member left:
/// • Mark them as a zombie (to be removed by the admin later).
/// If the admin left:
/// • Unsubscribe from PNs, delete the group public key, etc. as the group will be disbanded.
private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupControlMessage) {
val context = MessagingModuleConfiguration.shared.context
val storage = MessagingModuleConfiguration.shared.storage
@@ -506,11 +534,10 @@ private fun MessageReceiver.handleClosedGroupMemberLeft(message: ClosedGroupCont
// admin left the group of linked device left the group
disableLocalGroupAndUnsubscribe(groupPublicKey, groupID, userPublicKey)
} else {
val isCurrentUserAdmin = admins.contains(userPublicKey)
storage.updateMembers(groupID, updatedMemberList.map { Address.fromSerialized(it) })
if (isCurrentUserAdmin) {
MessageSender.generateAndSendNewEncryptionKeyPair(groupPublicKey, updatedMemberList)
}
// update zombie members
val zombies = storage.getZombieMember(groupID)
storage.updateZombieMembers(groupID, zombies.plus(senderPublicKey).map { Address.fromSerialized(it) })
}
// Notify the user
if (userLeft) {

View File

@@ -7,10 +7,8 @@ import nl.komponents.kovenant.functional.map
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.SnodeAPI
import org.session.libsignal.service.loki.utilities.getRandomElementOrNull
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.successBackground
@@ -71,11 +69,8 @@ class ClosedGroupPoller {
// ignore inactive group's messages
return@successBackground
}
messages.forEach { message ->
val rawMessageAsJSON = message as? Map<*, *>
val base64EncodedData = rawMessageAsJSON?.get("data") as? String
val data = base64EncodedData?.let { Base64.decode(it) } ?: return@forEach
val job = MessageReceiveJob(MessageWrapper.unwrap(data).toByteArray(), false)
messages.forEach { envelope ->
val job = MessageReceiveJob(envelope.toByteArray(), false)
JobQueue.shared.add(job)
}
}

View File

@@ -5,11 +5,9 @@ import nl.komponents.kovenant.functional.bind
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.jobs.MessageReceiveJob
import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.SnodeAPI
import org.session.libsession.snode.SnodeModule
import org.session.libsignal.service.loki.Snode
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log
import java.security.SecureRandom
import java.util.*

View File

@@ -20,9 +20,10 @@ import org.session.libsignal.service.internal.push.ProfileAvatarData
import org.session.libsignal.service.internal.push.PushAttachmentData
import org.session.libsignal.service.internal.push.http.DigestingRequestBody
import org.session.libsignal.service.internal.push.http.ProfileCipherOutputStreamFactory
import org.session.libsignal.service.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.retryIfNeeded
import org.session.libsignal.utilities.Hex
import org.session.libsignal.utilities.JsonUtil
import org.session.libsignal.service.loki.HTTP
import org.session.libsignal.service.loki.utilities.*
import org.session.libsignal.utilities.*
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.logging.Log

View File

@@ -12,11 +12,13 @@ import org.session.libsignal.utilities.logging.Log
import org.session.libsignal.utilities.Base64
import org.session.libsignal.utilities.*
import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.api.utilities.*
import org.session.libsignal.service.loki.*
import org.session.libsession.utilities.AESGCM.EncryptionResult
import org.session.libsession.utilities.getBodyForOnionRequest
import org.session.libsession.utilities.getHeadersForOnionRequest
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.Broadcaster
import org.session.libsignal.service.loki.HTTP
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.*
private typealias Path = List<Snode>

View File

@@ -10,9 +10,9 @@ import org.session.libsession.messaging.utilities.MessageWrapper
import org.session.libsession.snode.utilities.getRandomElement
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.loki.Snode
import org.session.libsignal.service.loki.api.utilities.HTTP
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.Broadcaster
import org.session.libsignal.service.loki.HTTP
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.Broadcaster
import org.session.libsignal.service.loki.utilities.prettifiedDescription
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
import org.session.libsignal.service.loki.utilities.retryIfNeeded

View File

@@ -1,7 +1,7 @@
package org.session.libsession.snode
import org.session.libsignal.service.loki.database.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.utilities.Broadcaster
import org.session.libsignal.service.loki.LokiAPIDatabaseProtocol
import org.session.libsignal.service.loki.Broadcaster
class SnodeModule(val storage: LokiAPIDatabaseProtocol, val broadcaster: Broadcaster) {

View File

@@ -1,3 +0,0 @@
package org.session.libsession.utilities.mentions
data class Mention(val publicKey: String, val displayName: String)