mirror of
https://github.com/oxen-io/session-android.git
synced 2025-08-12 16:37:41 +00:00
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:
@@ -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>
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
@@ -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 -> {
|
||||
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
|
@@ -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> {
|
||||
|
@@ -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) {
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
|
@@ -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.*
|
||||
|
@@ -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
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
|
@@ -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) {
|
||||
|
||||
|
@@ -1,3 +0,0 @@
|
||||
package org.session.libsession.utilities.mentions
|
||||
|
||||
data class Mention(val publicKey: String, val displayName: String)
|
||||
|
Reference in New Issue
Block a user