feat: adding basic invite for new groups handling and immediate response, need to add handling for response and actually sending the invites via UI possibly
This commit is contained in:
parent
352246842f
commit
2448af0b73
|
@ -35,6 +35,7 @@ import org.session.libsession.messaging.jobs.RetrieveProfileAvatarJob
|
|||
import org.session.libsession.messaging.messages.Destination
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.GroupUpdated
|
||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||
import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage
|
||||
import org.session.libsession.messaging.messages.signal.IncomingGroupMessage
|
||||
|
@ -50,6 +51,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage
|
|||
import org.session.libsession.messaging.open_groups.GroupMember
|
||||
import org.session.libsession.messaging.open_groups.OpenGroup
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
|
||||
import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage
|
||||
|
@ -76,6 +78,8 @@ import org.session.libsignal.crypto.ecc.DjbECPublicKey
|
|||
import org.session.libsignal.crypto.ecc.ECKeyPair
|
||||
import org.session.libsignal.messages.SignalServiceAttachmentPointer
|
||||
import org.session.libsignal.messages.SignalServiceGroup
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateInviteResponseMessage
|
||||
import org.session.libsignal.utilities.Base64
|
||||
import org.session.libsignal.utilities.Hex
|
||||
import org.session.libsignal.utilities.IdPrefix
|
||||
|
@ -1199,6 +1203,28 @@ open class Storage(
|
|||
override fun getMembers(groupPublicKey: String): List<LibSessionGroupMember> =
|
||||
configFactory.getGroupMemberConfig(SessionId.from(groupPublicKey))?.use { it.all() }?.toList() ?: emptyList()
|
||||
|
||||
override fun acceptClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId) {
|
||||
val recipient = Recipient.from(context, Address.fromSerialized(groupId.hexString()), false)
|
||||
val profileManager = SSKEnvironment.shared.profileManager
|
||||
val groups = configFactory.userGroups ?: return
|
||||
val closedGroupInfo = GroupInfo.ClosedGroupInfo(
|
||||
groupId, byteArrayOf(), authData, PRIORITY_VISIBLE
|
||||
)
|
||||
groups.set(closedGroupInfo)
|
||||
profileManager.setName(context, recipient, name)
|
||||
setRecipientApprovedMe(recipient, true)
|
||||
setRecipientApproved(recipient, true)
|
||||
getOrCreateThreadIdFor(recipient.address)
|
||||
pollerFactory.pollerFor(groupId)?.start()
|
||||
val invitingAdminAddress = Address.fromSerialized(invitingAdmin.hexString())
|
||||
val inviteResponse = GroupUpdateInviteResponseMessage.newBuilder()
|
||||
.setIsApproved(true)
|
||||
val responseData = DataMessage.GroupUpdateMessage.newBuilder()
|
||||
.setInviteResponse(inviteResponse)
|
||||
val responseMessage = GroupUpdated(responseData.build())
|
||||
MessageSender.send(responseMessage, invitingAdminAddress)
|
||||
}
|
||||
|
||||
override fun setServerCapabilities(server: String, capabilities: List<String>) {
|
||||
return DatabaseComponent.get(context).lokiAPIDatabase().setServerCapabilities(server, capabilities)
|
||||
}
|
||||
|
|
|
@ -13,8 +13,9 @@ class PollerFactory(private val scope: CoroutineScope,
|
|||
private val pollers = ConcurrentHashMap<SessionId, ClosedGroupPoller>()
|
||||
|
||||
fun pollerFor(sessionId: SessionId): ClosedGroupPoller? {
|
||||
val activeGroup = configFactory.userGroups?.getClosedGroup(sessionId.hexString()) ?: return null
|
||||
// TODO: add check for active group being invited / approved etc
|
||||
// Check if the group is currently in our config, don't start if it isn't
|
||||
configFactory.userGroups?.getClosedGroup(sessionId.hexString()) ?: return null
|
||||
|
||||
return pollers.getOrPut(sessionId) {
|
||||
ClosedGroupPoller(scope + SupervisorJob(), sessionId, configFactory)
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit cce0db485f5749a2891ce56170342c5b0e82272c
|
||||
Subproject commit e4b0358a50a65796ac5e2da4938505bc8ebf0313
|
|
@ -229,4 +229,18 @@ Java_network_loki_messenger_libsession_1util_GroupKeysConfig_currentHashes(JNIEn
|
|||
env->CallObjectMethod(our_list, push, hash_bytes);
|
||||
}
|
||||
return our_list;
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_network_loki_messenger_libsession_1util_GroupKeysConfig_makeSubAccount(JNIEnv *env,
|
||||
jobject thiz,
|
||||
jobject session_id,
|
||||
jboolean can_write,
|
||||
jboolean can_delete) {
|
||||
std::lock_guard lock{util::util_mutex_};
|
||||
auto ptr = ptrToKeys(env, thiz);
|
||||
auto deserialized_id = util::deserialize_session_id(env, session_id);
|
||||
auto new_subaccount_key = ptr->swarm_make_subaccount(deserialized_id.data(), can_write, can_delete);
|
||||
auto jbytes = util::bytes_from_ustring(env, new_subaccount_key);
|
||||
return jbytes;
|
||||
}
|
|
@ -332,4 +332,6 @@ class GroupKeysConfig(pointer: Long): ConfigSig(pointer) {
|
|||
|
||||
external fun keys(): Stack<ByteArray>
|
||||
|
||||
external fun makeSubAccount(sessionId: SessionId, canWrite: Boolean = true, canDelete: Boolean = false): ByteArray
|
||||
|
||||
}
|
|
@ -35,6 +35,7 @@ import org.session.libsignal.messages.SignalServiceGroup
|
|||
import org.session.libsignal.utilities.SessionId
|
||||
import org.session.libsignal.utilities.guava.Optional
|
||||
import network.loki.messenger.libsession_util.util.Contact as LibSessionContact
|
||||
import network.loki.messenger.libsession_util.util.GroupMember as LibSessionGroupMember
|
||||
|
||||
interface StorageProtocol {
|
||||
|
||||
|
@ -157,7 +158,8 @@ interface StorageProtocol {
|
|||
|
||||
// Closed Groups
|
||||
fun createNewGroup(groupName: String, groupDescription: String, members: Set<SessionId>): Optional<Recipient>
|
||||
fun getMembers(groupPublicKey: String): List<network.loki.messenger.libsession_util.util.GroupMember>
|
||||
fun getMembers(groupPublicKey: String): List<LibSessionGroupMember>
|
||||
fun acceptClosedGroupInvite(groupId: SessionId, name: String, authData: ByteArray, invitingAdmin: SessionId)
|
||||
|
||||
// Groups
|
||||
fun getAllGroups(includeInactive: Boolean): List<GroupRecord>
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package org.session.libsession.messaging.messages.control
|
||||
|
||||
import org.session.libsignal.protos.SignalServiceProtos.Content
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage
|
||||
import org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupUpdateMessage
|
||||
|
||||
class GroupUpdated(val inner: GroupUpdateMessage): ControlMessage() {
|
||||
|
||||
companion object {
|
||||
fun fromProto(message: GroupUpdateMessage): GroupUpdated = GroupUpdated(message)
|
||||
}
|
||||
|
||||
override fun toProto(): Content {
|
||||
val dataMessage = DataMessage.newBuilder()
|
||||
.setGroupUpdateMessage(inner)
|
||||
.build()
|
||||
return Content.newBuilder()
|
||||
.setDataMessage(dataMessage)
|
||||
.build()
|
||||
}
|
||||
}
|
|
@ -153,7 +153,8 @@ object MessageReceiver {
|
|||
MessageRequestResponse.fromProto(proto) ?:
|
||||
CallMessage.fromProto(proto) ?:
|
||||
SharedConfigurationMessage.fromProto(proto) ?:
|
||||
VisibleMessage.fromProto(proto) ?: run {
|
||||
VisibleMessage.fromProto(proto) ?:
|
||||
ClosedGroupMessage.fromProto(proto) ?: run {
|
||||
throw Error.UnknownMessage
|
||||
}
|
||||
val isUserBlindedSender = sender == openGroupPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(IdPrefix.BLINDED, it.publicKey.asBytes).hexString() }
|
||||
|
|
|
@ -12,6 +12,7 @@ import org.session.libsession.messaging.messages.control.CallMessage
|
|||
import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage
|
||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||
import org.session.libsession.messaging.messages.control.GroupUpdated
|
||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||
import org.session.libsession.messaging.messages.control.SharedConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||
|
@ -122,9 +123,15 @@ object MessageSender {
|
|||
message.profile = storage.getUserProfile()
|
||||
}
|
||||
// Convert it to protobuf
|
||||
val proto = message.toProto() ?: throw Error.ProtoConversionFailed
|
||||
val proto = message.toProto()?.toBuilder() ?: throw Error.ProtoConversionFailed
|
||||
if (message is GroupUpdated) {
|
||||
// Add all cases where we have to attach profile
|
||||
if (message.inner.hasInviteResponse()) {
|
||||
proto.mergeDataMessage(storage.getUserProfile().toProto())
|
||||
}
|
||||
}
|
||||
// Serialize the protobuf
|
||||
val plaintext = PushTransportDetails.getPaddedMessageBody(proto.toByteArray())
|
||||
val plaintext = PushTransportDetails.getPaddedMessageBody(proto.build().toByteArray())
|
||||
|
||||
// Envelope information
|
||||
val kind: SignalServiceProtos.Envelope.Type
|
||||
|
@ -157,7 +164,7 @@ object MessageSender {
|
|||
}
|
||||
is Destination.ClosedGroup -> {
|
||||
val groupKeys = configFactory.getGroupKeysConfig(SessionId.from(destination.publicKey)) ?: throw Error.NoKeyPair
|
||||
val envelope = MessageWrapper.createEnvelope(kind, message.sentTimestamp!!, senderPublicKey, proto.toByteArray())
|
||||
val envelope = MessageWrapper.createEnvelope(kind, message.sentTimestamp!!, senderPublicKey, proto.build().toByteArray())
|
||||
groupKeys.use { keys ->
|
||||
keys.encrypt(envelope.toByteArray())
|
||||
}
|
||||
|
@ -189,6 +196,7 @@ object MessageSender {
|
|||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val configFactory = MessagingModuleConfiguration.shared.configFactory
|
||||
val userPublicKey = storage.getUserPublicKey()
|
||||
val ourProfile = storage.getUserProfile()
|
||||
|
||||
// recipient will be set later, so initialize it as a function here
|
||||
val isSelfSend = { message.recipient == userPublicKey }
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.session.libsession.messaging.messages.control.ClosedGroupControlMessa
|
|||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||
import org.session.libsession.messaging.messages.control.DataExtractionNotification
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||
import org.session.libsession.messaging.messages.control.GroupUpdated
|
||||
import org.session.libsession.messaging.messages.control.MessageRequestResponse
|
||||
import org.session.libsession.messaging.messages.control.ReadReceipt
|
||||
import org.session.libsession.messaging.messages.control.TypingIndicator
|
||||
|
@ -51,6 +52,7 @@ import org.session.libsignal.utilities.guava.Optional
|
|||
import org.session.libsignal.utilities.removingIdPrefixIfNeeded
|
||||
import org.session.libsignal.utilities.toHexString
|
||||
import java.security.MessageDigest
|
||||
import java.security.SignatureException
|
||||
import java.util.LinkedList
|
||||
import kotlin.math.min
|
||||
|
||||
|
@ -68,6 +70,7 @@ fun MessageReceiver.handle(message: Message, proto: SignalServiceProtos.Content,
|
|||
is ReadReceipt -> handleReadReceipt(message)
|
||||
is TypingIndicator -> handleTypingIndicator(message)
|
||||
is ClosedGroupControlMessage -> handleClosedGroupControlMessage(message)
|
||||
is GroupUpdated -> handleNewLibSessionClosedGroupMessage(message)
|
||||
is ExpirationTimerUpdate -> handleExpirationTimerUpdate(message)
|
||||
is DataExtractionNotification -> handleDataExtractionNotification(message)
|
||||
is ConfigurationMessage -> handleConfigurationMessage(message)
|
||||
|
@ -516,6 +519,48 @@ private fun ClosedGroupControlMessage.getPublicKey(): String = kind!!.let { when
|
|||
is ClosedGroupControlMessage.Kind.NameChange -> groupPublicKey!!
|
||||
}}
|
||||
|
||||
private fun MessageReceiver.handleGroupUpdated(message: GroupUpdated) {
|
||||
when {
|
||||
message.inner.hasInviteMessage() -> handleNewLibSessionClosedGroupMessage(message)
|
||||
message.inner.hasInviteResponse() -> handleInviteResponse(message)
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleInviteResponse(message: GroupUpdated) {
|
||||
val sender = message.sender!!
|
||||
// val profile = message // maybe we do need data to be the inner so we can access profile
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val approved = message.inner.inviteResponse.isApproved
|
||||
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleNewLibSessionClosedGroupMessage(message: GroupUpdated) {
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val ourUserId = storage.getUserPublicKey()!!
|
||||
val invite = message.inner.inviteMessage
|
||||
val groupId = SessionId.from(invite.groupSessionId)
|
||||
verifyAdminSignature(groupId, invite.adminSignature.toByteArray(), "INVITE"+ourUserId+message.sentTimestamp!!)
|
||||
val adminId = SessionId.from(message.sender!!)
|
||||
// TODO: add the pending invite logic after testing initial group signing / message adding / encryption works for members as well
|
||||
// add the group
|
||||
storage.acceptClosedGroupInvite(groupId, invite.name, invite.memberAuthData.toByteArray(), adminId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Does nothing on successful signature verification, throws otherwise.
|
||||
* Assumes the signer is using the ed25519 group key signing key
|
||||
* @param groupSessionId the SessionId of the group to check the signature against
|
||||
* @param signatureData the byte array supplied to us through a protobuf message from the admin
|
||||
* @param messageToValidate the expected values used for this signature generation, often something like `INVITE||{inviteeSessionId}||{timestamp}`
|
||||
* @throws SignatureException if signature cannot be verified with given parameters
|
||||
*/
|
||||
private fun verifyAdminSignature(groupSessionId: SessionId, signatureData: ByteArray, messageToValidate: String) {
|
||||
val groupPubKey = groupSessionId.pubKeyBytes
|
||||
if (!SodiumUtilities.verifySignature(signatureData, groupPubKey, messageToValidate.encodeToByteArray())) {
|
||||
throw SignatureException("Verification failed for signature data")
|
||||
}
|
||||
}
|
||||
|
||||
private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMessage) {
|
||||
val kind = message.kind!! as? ClosedGroupControlMessage.Kind.New ?: return
|
||||
val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false)
|
||||
|
|
|
@ -230,4 +230,11 @@ object SodiumUtilities {
|
|||
} else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true only if the signature verified successfully
|
||||
*/
|
||||
fun verifySignature(signature: ByteArray, publicKey: ByteArray, messageToVerify: ByteArray): Boolean {
|
||||
return sodium.cryptoSignVerifyDetached(signature, messageToVerify, messageToVerify.size, publicKey)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -120,13 +120,6 @@ message DataMessage {
|
|||
required string name = 3;
|
||||
}
|
||||
|
||||
message GroupMessage {
|
||||
optional GroupDeleteMessage deleteMessage = 31;
|
||||
optional GroupMemberLeftMessage memberLeftMessage = 32;
|
||||
optional GroupInviteMessage inviteMessage = 33;
|
||||
optional GroupPromoteMessage promoteMessage = 34;
|
||||
}
|
||||
|
||||
// New closed group update messages
|
||||
message GroupUpdateMessage {
|
||||
optional GroupUpdateInviteMessage inviteMessage = 1;
|
||||
|
@ -274,7 +267,7 @@ message DataMessage {
|
|||
optional ClosedGroupControlMessage closedGroupControlMessage = 104;
|
||||
optional string syncTarget = 105;
|
||||
optional bool blocksCommunityMessageRequests = 106;
|
||||
optional GroupMessage groupMessage = 120;
|
||||
optional GroupUpdateMessage groupUpdateMessage = 120;
|
||||
}
|
||||
|
||||
message GroupDeleteMessage {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue