mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
Minor V2 open group refactoring
This commit is contained in:
parent
21698fcba5
commit
d8932416f1
@ -257,7 +257,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf(threadId)) { cursor ->
|
return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf(threadId)) { cursor ->
|
||||||
val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat)
|
val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat)
|
||||||
OpenGroupV2.fromJson(publicChatAsJson)
|
OpenGroupV2.fromJSON(publicChatAsJson)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +195,7 @@ class EnterChatURLFragment : Fragment() {
|
|||||||
chip.chipIcon = drawable
|
chip.chipIcon = drawable
|
||||||
chip.text = defaultGroup.name
|
chip.text = defaultGroup.name
|
||||||
chip.setOnClickListener {
|
chip.setOnClickListener {
|
||||||
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(defaultGroup.toJoinUrl())
|
(requireActivity() as JoinPublicChatActivity).joinPublicChatIfPossible(defaultGroup.joinURL)
|
||||||
}
|
}
|
||||||
defaultRoomsGridLayout.addView(chip)
|
defaultRoomsGridLayout.addView(chip)
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
val threadID = cursor.getLong(threadID)
|
val threadID = cursor.getLong(threadID)
|
||||||
val string = cursor.getString(publicChat)
|
val string = cursor.getString(publicChat)
|
||||||
val openGroup = OpenGroupV2.fromJson(string)
|
val openGroup = OpenGroupV2.fromJSON(string)
|
||||||
if (openGroup != null) result[threadID] = openGroup
|
if (openGroup != null) result[threadID] = openGroup
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -100,7 +100,7 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor ->
|
return database.get(publicChatTable, "${Companion.threadID} = ?", arrayOf(threadID.toString())) { cursor ->
|
||||||
val json = cursor.getString(publicChat)
|
val json = cursor.getString(publicChat)
|
||||||
OpenGroupV2.fromJson(json)
|
OpenGroupV2.fromJSON(json)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
package org.session.libsession.messaging.file_server
|
package org.session.libsession.messaging.file_server
|
||||||
|
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
|
||||||
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
import org.session.libsession.messaging.open_groups.OpenGroupAPIV2
|
||||||
import org.session.libsession.snode.OnionRequestAPI
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
import org.session.libsignal.service.loki.HTTP
|
import org.session.libsignal.service.loki.HTTP
|
||||||
import org.session.libsignal.service.loki.utilities.retryIfNeeded
|
|
||||||
import org.session.libsignal.utilities.Base64
|
import org.session.libsignal.utilities.Base64
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.session.libsignal.utilities.logging.Log
|
import org.session.libsignal.utilities.logging.Log
|
||||||
@ -51,7 +48,7 @@ object FileServerAPIV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun send(request: Request): Promise<Map<*, *>, Exception> {
|
private fun send(request: Request): Promise<Map<*, *>, Exception> {
|
||||||
val parsed = HttpUrl.parse(DEFAULT_SERVER) ?: return Promise.ofFail(OpenGroupAPIV2.Error.INVALID_URL)
|
val parsed = HttpUrl.parse(DEFAULT_SERVER) ?: return Promise.ofFail(OpenGroupAPIV2.Error.InvalidURL)
|
||||||
val urlBuilder = HttpUrl.Builder()
|
val urlBuilder = HttpUrl.Builder()
|
||||||
.scheme(parsed.scheme())
|
.scheme(parsed.scheme())
|
||||||
.host(parsed.host())
|
.host(parsed.host())
|
||||||
@ -91,7 +88,7 @@ object FileServerAPIV2 {
|
|||||||
val parameters = mapOf("file" to base64EncodedFile)
|
val parameters = mapOf("file" to base64EncodedFile)
|
||||||
val request = Request(verb = HTTP.Verb.POST, endpoint = "files", parameters = parameters)
|
val request = Request(verb = HTTP.Verb.POST, endpoint = "files", parameters = parameters)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
json["result"] as? Long ?: throw OpenGroupAPIV2.Error.PARSING_FAILED
|
json["result"] as? Long ?: throw OpenGroupAPIV2.Error.ParsingFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class ConfigurationMessage(var closedGroups: List<ClosedGroup>, var openGroups:
|
|||||||
val threadID = storage.getThreadID(group.encodedId) ?: continue
|
val threadID = storage.getThreadID(group.encodedId) ?: continue
|
||||||
val openGroup = storage.getOpenGroup(threadID)
|
val openGroup = storage.getOpenGroup(threadID)
|
||||||
val openGroupV2 = storage.getV2OpenGroup(threadID)
|
val openGroupV2 = storage.getV2OpenGroup(threadID)
|
||||||
val shareUrl = openGroup?.server ?: openGroupV2?.toJoinUrl() ?: continue
|
val shareUrl = openGroup?.server ?: openGroupV2?.joinURL ?: continue
|
||||||
openGroups.add(shareUrl)
|
openGroups.add(shareUrl)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,7 +33,12 @@ class ExpirationTimerUpdate() : ControlMessage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal constructor(syncTarget: String? = null, duration: Int) : this() {
|
internal constructor(duration: Int) : this() {
|
||||||
|
this.syncTarget = null
|
||||||
|
this.duration = duration
|
||||||
|
}
|
||||||
|
|
||||||
|
internal constructor(syncTarget: String, duration: Int) : this() {
|
||||||
this.syncTarget = syncTarget
|
this.syncTarget = syncTarget
|
||||||
this.duration = duration
|
this.duration = duration
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming
|
|||||||
import com.fasterxml.jackson.databind.type.TypeFactory
|
import com.fasterxml.jackson.databind.type.TypeFactory
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import nl.komponents.kovenant.Kovenant
|
|
||||||
import nl.komponents.kovenant.Promise
|
import nl.komponents.kovenant.Promise
|
||||||
import nl.komponents.kovenant.functional.bind
|
import nl.komponents.kovenant.functional.bind
|
||||||
import nl.komponents.kovenant.functional.map
|
import nl.komponents.kovenant.functional.map
|
||||||
@ -14,7 +13,6 @@ import okhttp3.HttpUrl
|
|||||||
import okhttp3.MediaType
|
import okhttp3.MediaType
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
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.snode.OnionRequestAPI
|
||||||
import org.session.libsession.utilities.AESGCM
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsignal.service.loki.HTTP
|
import org.session.libsignal.service.loki.HTTP
|
||||||
@ -29,62 +27,48 @@ import org.whispersystems.curve25519.Curve25519
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
object OpenGroupAPIV2 {
|
object OpenGroupAPIV2 {
|
||||||
|
|
||||||
private val moderators: HashMap<String, Set<String>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
private val moderators: HashMap<String, Set<String>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
||||||
const val DEFAULT_SERVER = "http://116.203.70.33"
|
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
||||||
private const val DEFAULT_SERVER_PUBLIC_KEY = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238"
|
|
||||||
|
|
||||||
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
|
val defaultRooms = MutableSharedFlow<List<DefaultGroup>>(replay = 1)
|
||||||
|
|
||||||
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
private const val DEFAULT_SERVER_PUBLIC_KEY = "a03c383cf63c3c4efe67acc52112a6dd734b3a946b9545f488aaa93da7991238"
|
||||||
|
const val DEFAULT_SERVER = "http://116.203.70.33"
|
||||||
|
|
||||||
sealed class Error : Exception() {
|
sealed class Error : Exception() {
|
||||||
object GENERIC : Error()
|
object Generic : Error()
|
||||||
object PARSING_FAILED : Error()
|
object ParsingFailed : Error()
|
||||||
object DECRYPTION_FAILED : Error()
|
object DecryptionFailed : Error()
|
||||||
object SIGNING_FAILED : Error()
|
object SigningFailed : Error()
|
||||||
object INVALID_URL : Error()
|
object InvalidURL : Error()
|
||||||
object NO_PUBLIC_KEY : Error()
|
object NoPublicKey : Error()
|
||||||
|
|
||||||
fun errorDescription() = when (this) {
|
fun errorDescription() = when (this) {
|
||||||
Error.GENERIC -> "An error occurred."
|
Error.Generic -> "An error occurred."
|
||||||
Error.PARSING_FAILED -> "Invalid response."
|
Error.ParsingFailed -> "Invalid response."
|
||||||
Error.DECRYPTION_FAILED -> "Couldn't decrypt response."
|
Error.DecryptionFailed -> "Couldn't decrypt response."
|
||||||
Error.SIGNING_FAILED -> "Couldn't sign message."
|
Error.SigningFailed -> "Couldn't sign message."
|
||||||
Error.INVALID_URL -> "Invalid URL."
|
Error.InvalidURL -> "Invalid URL."
|
||||||
Error.NO_PUBLIC_KEY -> "Couldn't find server public key."
|
Error.NoPublicKey -> "Couldn't find server public key."
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class DefaultGroup(val id: String,
|
data class DefaultGroup(val id: String, val name: String, val image: ByteArray?) {
|
||||||
val name: String,
|
|
||||||
val image: ByteArray?) {
|
val joinURL: String get() = "$DEFAULT_SERVER/$id?public_key=$DEFAULT_SERVER_PUBLIC_KEY"
|
||||||
fun toJoinUrl(): String = "$DEFAULT_SERVER/$id?public_key=$DEFAULT_SERVER_PUBLIC_KEY"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Info(
|
data class Info(val id: String, val name: String, val imageID: String?)
|
||||||
val id: String,
|
|
||||||
val name: String,
|
|
||||||
val imageID: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
|
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
|
||||||
data class CompactPollRequest(val roomId: String,
|
data class CompactPollRequest(val roomID: String, val authToken: String, val fromDeletionServerID: Long?, val fromMessageServerID: Long?)
|
||||||
val authToken: String,
|
data class CompactPollResult(val messages: List<OpenGroupMessageV2>, val deletions: List<Long>, val moderators: List<String>)
|
||||||
val fromDeletionServerId: Long?,
|
|
||||||
val fromMessageServerId: Long?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class CompactPollResult(val messages: List<OpenGroupMessageV2>,
|
|
||||||
val deletions: List<Long>,
|
|
||||||
val moderators: List<String>
|
|
||||||
)
|
|
||||||
|
|
||||||
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
|
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class)
|
||||||
data class MessageDeletion @JvmOverloads constructor(val id: Long = 0,
|
data class MessageDeletion
|
||||||
val deletedMessageId: Long = 0
|
@JvmOverloads constructor(val id: Long = 0, val deletedMessageId: Long = 0
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = MessageDeletion()
|
val EMPTY = MessageDeletion()
|
||||||
}
|
}
|
||||||
@ -99,38 +83,37 @@ object OpenGroupAPIV2 {
|
|||||||
val parameters: Any? = null,
|
val parameters: Any? = null,
|
||||||
val headers: Map<String, String> = mapOf(),
|
val headers: Map<String, String> = mapOf(),
|
||||||
val isAuthRequired: Boolean = true,
|
val isAuthRequired: Boolean = true,
|
||||||
// Always `true` under normal circumstances. You might want to disable
|
/**
|
||||||
// this when running over Lokinet.
|
* Always `true` under normal circumstances. You might want to disable
|
||||||
|
* this when running over Lokinet.
|
||||||
|
*/
|
||||||
val useOnionRouting: Boolean = true
|
val useOnionRouting: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun createBody(parameters: Any?): RequestBody? {
|
private fun createBody(parameters: Any?): RequestBody? {
|
||||||
if (parameters == null) return null
|
if (parameters == null) return null
|
||||||
|
|
||||||
val parametersAsJSON = JsonUtil.toJson(parameters)
|
val parametersAsJSON = JsonUtil.toJson(parameters)
|
||||||
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
|
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun send(request: Request, isJsonRequired: Boolean = true): Promise<Map<*, *>, Exception> {
|
private fun send(request: Request, isJsonRequired: Boolean = true): Promise<Map<*, *>, Exception> {
|
||||||
val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL)
|
val url = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.InvalidURL)
|
||||||
val urlBuilder = HttpUrl.Builder()
|
val urlBuilder = HttpUrl.Builder()
|
||||||
.scheme(parsed.scheme())
|
.scheme(url.scheme())
|
||||||
.host(parsed.host())
|
.host(url.host())
|
||||||
.port(parsed.port())
|
.port(url.port())
|
||||||
.addPathSegments(request.endpoint)
|
.addPathSegments(request.endpoint)
|
||||||
|
|
||||||
if (request.verb == GET) {
|
if (request.verb == GET) {
|
||||||
for ((key, value) in request.queryParameters) {
|
for ((key, value) in request.queryParameters) {
|
||||||
urlBuilder.addQueryParameter(key, value)
|
urlBuilder.addQueryParameter(key, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun execute(token: String?): Promise<Map<*, *>, Exception> {
|
fun execute(token: String?): Promise<Map<*, *>, Exception> {
|
||||||
val requestBuilder = okhttp3.Request.Builder()
|
val requestBuilder = okhttp3.Request.Builder()
|
||||||
.url(urlBuilder.build())
|
.url(urlBuilder.build())
|
||||||
.headers(Headers.of(request.headers))
|
.headers(Headers.of(request.headers))
|
||||||
if (request.isAuthRequired) {
|
if (request.isAuthRequired) {
|
||||||
if (token.isNullOrEmpty()) throw IllegalStateException("No auth token for request")
|
if (token.isNullOrEmpty()) throw IllegalStateException("No auth token for request.")
|
||||||
requestBuilder.header("Authorization", token)
|
requestBuilder.header("Authorization", token)
|
||||||
}
|
}
|
||||||
when (request.verb) {
|
when (request.verb) {
|
||||||
@ -139,25 +122,25 @@ object OpenGroupAPIV2 {
|
|||||||
POST -> requestBuilder.post(createBody(request.parameters)!!)
|
POST -> requestBuilder.post(createBody(request.parameters)!!)
|
||||||
DELETE -> requestBuilder.delete(createBody(request.parameters))
|
DELETE -> requestBuilder.delete(createBody(request.parameters))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!request.room.isNullOrEmpty()) {
|
if (!request.room.isNullOrEmpty()) {
|
||||||
requestBuilder.header("Room", request.room)
|
requestBuilder.header("Room", request.room)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.useOnionRouting) {
|
if (request.useOnionRouting) {
|
||||||
val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
|
val publicKey = MessagingModuleConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
|
||||||
?: return Promise.ofFail(Error.NO_PUBLIC_KEY)
|
?: return Promise.ofFail(Error.NoPublicKey)
|
||||||
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey, isJSONRequired = isJsonRequired)
|
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey, isJSONRequired = isJsonRequired).fail { e ->
|
||||||
.fail { e ->
|
// A 401 means that we didn't provide a (valid) auth token for a route that required one. We use this as an
|
||||||
if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException && e.statusCode == 401) {
|
// indication that the token we're using has expired. Note that a 403 has a different meaning; it means that
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
// we provided a valid token but it doesn't have a high enough permission level for the route in question.
|
||||||
if (request.room != null) {
|
if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException && e.statusCode == 401) {
|
||||||
storage.removeAuthToken("${request.server}.${request.room}")
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
} else {
|
if (request.room != null) {
|
||||||
storage.removeAuthToken(request.server)
|
storage.removeAuthToken("${request.server}.${request.room}")
|
||||||
}
|
} else {
|
||||||
}
|
storage.removeAuthToken(request.server)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||||
}
|
}
|
||||||
@ -172,52 +155,51 @@ object OpenGroupAPIV2 {
|
|||||||
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
|
fun downloadOpenGroupProfilePicture(roomID: String, server: String): Promise<ByteArray, Exception> {
|
||||||
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
|
val request = Request(verb = GET, room = roomID, server = server, endpoint = "rooms/$roomID/image", isAuthRequired = false)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val result = json["result"] as? String ?: throw Error.PARSING_FAILED
|
val result = json["result"] as? String ?: throw Error.ParsingFailed
|
||||||
decode(result)
|
decode(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region Authorization
|
||||||
fun getAuthToken(room: String, server: String): Promise<String, Exception> {
|
fun getAuthToken(room: String, server: String): Promise<String, Exception> {
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
return storage.getAuthToken(room, server)?.let {
|
return storage.getAuthToken(room, server)?.let {
|
||||||
Promise.of(it)
|
Promise.of(it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
requestNewAuthToken(room, server)
|
requestNewAuthToken(room, server)
|
||||||
.bind { claimAuthToken(it, room, server) }
|
.bind { claimAuthToken(it, room, server) }
|
||||||
.success { authToken ->
|
.success { authToken ->
|
||||||
storage.setAuthToken(room, server, authToken)
|
storage.setAuthToken(room, server, authToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
||||||
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair()
|
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair()
|
||||||
?: return Promise.ofFail(Error.GENERIC)
|
?: return Promise.ofFail(Error.Generic)
|
||||||
val queryParameters = mutableMapOf("public_key" to publicKey)
|
val queryParameters = mutableMapOf( "public_key" to publicKey )
|
||||||
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
val request = Request(GET, room, server, "auth_token_challenge", queryParameters, isAuthRequired = false, parameters = null)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.PARSING_FAILED
|
val challenge = json["challenge"] as? Map<*, *> ?: throw Error.ParsingFailed
|
||||||
val base64EncodedCiphertext = challenge["ciphertext"] as? String
|
val base64EncodedCiphertext = challenge["ciphertext"] as? String ?: throw Error.ParsingFailed
|
||||||
?: throw Error.PARSING_FAILED
|
val base64EncodedEphemeralPublicKey = challenge["ephemeral_public_key"] as? String ?: throw Error.ParsingFailed
|
||||||
val base64EncodedEphemeralPublicKey = challenge["ephemeral_public_key"] as? String
|
|
||||||
?: throw Error.PARSING_FAILED
|
|
||||||
val ciphertext = decode(base64EncodedCiphertext)
|
val ciphertext = decode(base64EncodedCiphertext)
|
||||||
val ephemeralPublicKey = decode(base64EncodedEphemeralPublicKey)
|
val ephemeralPublicKey = decode(base64EncodedEphemeralPublicKey)
|
||||||
val symmetricKey = AESGCM.generateSymmetricKey(ephemeralPublicKey, privateKey)
|
val symmetricKey = AESGCM.generateSymmetricKey(ephemeralPublicKey, privateKey)
|
||||||
val tokenAsData = try {
|
val tokenAsData = try {
|
||||||
AESGCM.decrypt(ciphertext, symmetricKey)
|
AESGCM.decrypt(ciphertext, symmetricKey)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
throw Error.DECRYPTION_FAILED
|
throw Error.DecryptionFailed
|
||||||
}
|
}
|
||||||
tokenAsData.toHexString()
|
tokenAsData.toHexString()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
|
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
|
||||||
val parameters = mapOf("public_key" to MessagingModuleConfiguration.shared.storage.getUserPublicKey()!!)
|
val parameters = mapOf( "public_key" to MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! )
|
||||||
val headers = mapOf("Authorization" to authToken)
|
val headers = mapOf( "Authorization" to authToken )
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
|
val request = Request(verb = POST, room = room, server = server, endpoint = "claim_auth_token",
|
||||||
parameters = parameters, headers = headers, isAuthRequired = false)
|
parameters = parameters, headers = headers, isAuthRequired = false)
|
||||||
return send(request).map { authToken }
|
return send(request).map { authToken }
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -227,33 +209,36 @@ object OpenGroupAPIV2 {
|
|||||||
MessagingModuleConfiguration.shared.storage.removeAuthToken(room, server)
|
MessagingModuleConfiguration.shared.storage.removeAuthToken(room, server)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
// region Sending
|
// region Upload/Download
|
||||||
fun upload(file: ByteArray, room: String, server: String): Promise<Long, Exception> {
|
fun upload(file: ByteArray, room: String, server: String): Promise<Long, Exception> {
|
||||||
val base64EncodedFile = encodeBytes(file)
|
val base64EncodedFile = encodeBytes(file)
|
||||||
val parameters = mapOf("file" to base64EncodedFile)
|
val parameters = mapOf( "file" to base64EncodedFile )
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "files", parameters = parameters)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
json["result"] as? Long ?: throw Error.PARSING_FAILED
|
json["result"] as? Long ?: throw Error.ParsingFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED
|
val base64EncodedFile = json["result"] as? String ?: throw Error.ParsingFailed
|
||||||
decode(base64EncodedFile) ?: throw Error.PARSING_FAILED
|
decode(base64EncodedFile) ?: throw Error.ParsingFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Sending
|
||||||
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
||||||
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SigningFailed)
|
||||||
val jsonMessage = signedMessage.toJSON()
|
val jsonMessage = signedMessage.toJSON()
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = jsonMessage)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
|
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String, Any>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.ParsingFailed
|
||||||
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.ParsingFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
@ -268,10 +253,9 @@ object OpenGroupAPIV2 {
|
|||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters)
|
val request = Request(verb = GET, room = room, server = server, endpoint = "messages", queryParameters = queryParameters)
|
||||||
return send(request).map { jsonList ->
|
return send(request).map { jsonList ->
|
||||||
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String, Any>>
|
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String, Any>>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.ParsingFailed
|
||||||
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
val lastMessageServerID = storage.getLastMessageServerId(room, server) ?: 0
|
||||||
|
var currentLastMessageServerID = lastMessageServerID
|
||||||
var currentMax = lastMessageServerId
|
|
||||||
val messages = rawMessages.mapNotNull { json ->
|
val messages = rawMessages.mapNotNull { json ->
|
||||||
try {
|
try {
|
||||||
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
|
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
|
||||||
@ -285,15 +269,15 @@ object OpenGroupAPIV2 {
|
|||||||
Log.d("Loki", "Ignoring message with invalid signature")
|
Log.d("Loki", "Ignoring message with invalid signature")
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
if (message.serverID > lastMessageServerId) {
|
if (message.serverID > lastMessageServerID) {
|
||||||
currentMax = message.serverID
|
currentLastMessageServerID = message.serverID
|
||||||
}
|
}
|
||||||
message
|
message
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storage.setLastMessageServerId(room, server, currentMax)
|
storage.setLastMessageServerId(room, server, currentLastMessageServerID)
|
||||||
messages
|
messages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -304,7 +288,7 @@ object OpenGroupAPIV2 {
|
|||||||
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "messages/$serverID")
|
||||||
return send(request).map {
|
return send(request).map {
|
||||||
Log.d("Loki", "Deleted server message")
|
Log.d("Loki", "Message deletion successful.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +302,7 @@ object OpenGroupAPIV2 {
|
|||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
|
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
|
||||||
val idsAsString = JsonUtil.toJson(json["ids"])
|
val idsAsString = JsonUtil.toJson(json["ids"])
|
||||||
val serverIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.PARSING_FAILED
|
val serverIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.ParsingFailed
|
||||||
val lastMessageServerId = storage.getLastDeletionServerId(room, server) ?: 0
|
val lastMessageServerId = storage.getLastDeletionServerId(room, server) ?: 0
|
||||||
val serverID = serverIDs.maxByOrNull {it.id } ?: MessageDeletion.EMPTY
|
val serverID = serverIDs.maxByOrNull {it.id } ?: MessageDeletion.EMPTY
|
||||||
if (serverID.id > lastMessageServerId) {
|
if (serverID.id > lastMessageServerId) {
|
||||||
@ -338,7 +322,7 @@ object OpenGroupAPIV2 {
|
|||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String>
|
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String>
|
||||||
?: throw Error.PARSING_FAILED
|
?: throw Error.ParsingFailed
|
||||||
val id = "$server.$room"
|
val id = "$server.$room"
|
||||||
handleModerators(id, moderatorsJson)
|
handleModerators(id, moderatorsJson)
|
||||||
moderatorsJson
|
moderatorsJson
|
||||||
@ -347,90 +331,86 @@ object OpenGroupAPIV2 {
|
|||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val parameters = mapOf("public_key" to publicKey)
|
val parameters = mapOf( "public_key" to publicKey )
|
||||||
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
|
val request = Request(verb = POST, room = room, server = server, endpoint = "block_list", parameters = parameters)
|
||||||
return send(request).map {
|
return send(request).map {
|
||||||
Log.d("Loki", "Banned user $publicKey from $server.$room")
|
Log.d("Loki", "Banned user: $publicKey from: $server.$room.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
fun unban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||||
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
|
||||||
return send(request).map {
|
return send(request).map {
|
||||||
Log.d("Loki", "Unbanned user $publicKey from $server.$room")
|
Log.d("Loki", "Unbanned user: $publicKey from: $server.$room")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isUserModerator(publicKey: String, room: String, server: String): Boolean =
|
fun isUserModerator(publicKey: String, room: String, server: String): Boolean =
|
||||||
moderators["$server.$room"]?.contains(publicKey) ?: false
|
moderators["$server.$room"]?.contains(publicKey) ?: false
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
// region General
|
// region General
|
||||||
@Suppress("UNCHECKED_CAST")
|
@Suppress("UNCHECKED_CAST")
|
||||||
fun getCompactPoll(rooms: List<String>, server: String): Promise<Map<String, CompactPollResult>, Exception> {
|
fun getCompactPoll(rooms: List<String>, server: String): Promise<Map<String, CompactPollResult>, Exception> {
|
||||||
val requestAuths = rooms.associateWith { room -> getAuthToken(room, server) }
|
val authTokenRequests = rooms.associateWith { room -> getAuthToken(room, server) }
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
val requests = rooms.mapNotNull { room ->
|
val requests = rooms.mapNotNull { room ->
|
||||||
val authToken = try {
|
val authToken = try {
|
||||||
requestAuths[room]?.get()
|
authTokenRequests[room]?.get()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Failed to get auth token for $room", e)
|
Log.e("Loki", "Failed to get auth token for $room.", e)
|
||||||
null
|
null
|
||||||
} ?: return@mapNotNull null
|
} ?: return@mapNotNull null
|
||||||
|
CompactPollRequest(
|
||||||
CompactPollRequest(roomId = room,
|
roomID = room,
|
||||||
authToken = authToken,
|
authToken = authToken,
|
||||||
fromDeletionServerId = storage.getLastDeletionServerId(room, server),
|
fromDeletionServerID = storage.getLastDeletionServerId(room, server),
|
||||||
fromMessageServerId = storage.getLastMessageServerId(room, server)
|
fromMessageServerID = storage.getLastMessageServerId(room, server)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val request = Request(verb = POST, room = null, server = server, endpoint = "compact_poll", isAuthRequired = false, parameters = mapOf("requests" to requests))
|
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 { json ->
|
return send(request = request).map { json ->
|
||||||
val results = json["results"] as? List<*> ?: throw Error.PARSING_FAILED
|
val results = json["results"] as? List<*> ?: throw Error.ParsingFailed
|
||||||
|
results.mapNotNull { json ->
|
||||||
results.mapNotNull { roomJson ->
|
if (json !is Map<*,*>) return@mapNotNull null
|
||||||
if (roomJson !is Map<*,*>) return@mapNotNull null
|
val roomID = json["room_id"] as? String ?: return@mapNotNull null
|
||||||
val roomId = roomJson["room_id"] as? String ?: return@mapNotNull null
|
// A 401 means that we didn't provide a (valid) auth token for a route that required one. We use this as an
|
||||||
|
// indication that the token we're using has expired. Note that a 403 has a different meaning; it means that
|
||||||
// check the status was fine
|
// we provided a valid token but it doesn't have a high enough permission level for the route in question.
|
||||||
val statusCode = roomJson["status_code"] as? Int ?: return@mapNotNull null
|
val statusCode = json["status_code"] as? Int ?: return@mapNotNull null
|
||||||
if (statusCode == 401) {
|
if (statusCode == 401) {
|
||||||
// delete auth token and return null
|
// delete auth token and return null
|
||||||
storage.removeAuthToken(roomId, server)
|
storage.removeAuthToken(roomID, server)
|
||||||
}
|
}
|
||||||
|
// Moderators
|
||||||
// check and store mods
|
val moderators = json["moderators"] as? List<String> ?: return@mapNotNull null
|
||||||
val moderators = roomJson["moderators"] as? List<String> ?: return@mapNotNull null
|
handleModerators("$server.$roomID", moderators)
|
||||||
handleModerators("$server.$roomId", moderators)
|
// Deletions
|
||||||
|
|
||||||
// get deletions
|
|
||||||
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
|
val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java)
|
||||||
val idsAsString = JsonUtil.toJson(roomJson["deletions"])
|
val idsAsString = JsonUtil.toJson(json["deletions"])
|
||||||
val deletedServerIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.PARSING_FAILED
|
val deletedServerIDs = JsonUtil.fromJson<List<MessageDeletion>>(idsAsString, type) ?: throw Error.ParsingFailed
|
||||||
val lastDeletionServerId = storage.getLastDeletionServerId(roomId, server) ?: 0
|
val lastDeletionServerID = storage.getLastDeletionServerId(roomID, server) ?: 0
|
||||||
val serverID = deletedServerIDs.maxByOrNull {it.id } ?: MessageDeletion.EMPTY
|
val serverID = deletedServerIDs.maxByOrNull {it.id } ?: MessageDeletion.EMPTY
|
||||||
if (serverID.id > lastDeletionServerId) {
|
if (serverID.id > lastDeletionServerID) {
|
||||||
storage.setLastDeletionServerId(roomId, server, serverID.id)
|
storage.setLastDeletionServerId(roomID, server, serverID.id)
|
||||||
}
|
}
|
||||||
|
// Messages
|
||||||
// get messages
|
val rawMessages = json["messages"] as? List<Map<String, Any>> ?: return@mapNotNull null
|
||||||
val rawMessages = roomJson["messages"] as? List<Map<String, Any>> ?: return@mapNotNull null // parsing failed
|
val lastMessageServerID = storage.getLastMessageServerId(roomID, server) ?: 0
|
||||||
|
var currentLastMessageServerID = lastMessageServerID
|
||||||
val lastMessageServerId = storage.getLastMessageServerId(roomId, server) ?: 0
|
|
||||||
var currentMax = lastMessageServerId
|
|
||||||
val messages = rawMessages.mapNotNull { rawMessage ->
|
val messages = rawMessages.mapNotNull { rawMessage ->
|
||||||
val message = OpenGroupMessageV2.fromJSON(rawMessage)?.apply {
|
val message = OpenGroupMessageV2.fromJSON(rawMessage)?.apply {
|
||||||
currentMax = maxOf(currentMax,this.serverID ?: 0)
|
currentLastMessageServerID = maxOf(currentLastMessageServerID,this.serverID ?: 0)
|
||||||
}
|
}
|
||||||
|
// TODO: We need to check the signature here...
|
||||||
message
|
message
|
||||||
}
|
}
|
||||||
storage.setLastMessageServerId(roomId, server, currentMax)
|
storage.setLastMessageServerId(roomID, server, currentLastMessageServerID)
|
||||||
roomId to CompactPollResult(
|
roomID to CompactPollResult(
|
||||||
messages = messages,
|
messages = messages,
|
||||||
deletions = deletedServerIDs.map { it.deletedMessageId },
|
deletions = deletedServerIDs.map { it.deletedMessageId },
|
||||||
moderators = moderators
|
moderators = moderators
|
||||||
)
|
)
|
||||||
}.toMap()
|
}.toMap()
|
||||||
}
|
}
|
||||||
@ -443,7 +423,7 @@ object OpenGroupAPIV2 {
|
|||||||
val earlyGroups = groups.map { group ->
|
val earlyGroups = groups.map { group ->
|
||||||
DefaultGroup(group.id, group.name, null)
|
DefaultGroup(group.id, group.name, null)
|
||||||
}
|
}
|
||||||
// see if we have any cached rooms, and if they already have images, don't overwrite with early non-image results
|
// See if we have any cached rooms, and if they already have images don't overwrite them with early non-image results
|
||||||
defaultRooms.replayCache.firstOrNull()?.let { replayed ->
|
defaultRooms.replayCache.firstOrNull()?.let { replayed ->
|
||||||
if (replayed.none { it.image?.isNotEmpty() == true}) {
|
if (replayed.none { it.image?.isNotEmpty() == true}) {
|
||||||
defaultRooms.tryEmit(earlyGroups)
|
defaultRooms.tryEmit(earlyGroups)
|
||||||
@ -452,12 +432,11 @@ object OpenGroupAPIV2 {
|
|||||||
val images = groups.map { group ->
|
val images = groups.map { group ->
|
||||||
group.id to downloadOpenGroupProfilePicture(group.id, DEFAULT_SERVER)
|
group.id to downloadOpenGroupProfilePicture(group.id, DEFAULT_SERVER)
|
||||||
}.toMap()
|
}.toMap()
|
||||||
|
|
||||||
groups.map { group ->
|
groups.map { group ->
|
||||||
val image = try {
|
val image = try {
|
||||||
images[group.id]!!.get()
|
images[group.id]!!.get()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// no image or image failed to download
|
// No image or image failed to download
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
DefaultGroup(group.id, group.name, image)
|
DefaultGroup(group.id, group.name, image)
|
||||||
@ -470,9 +449,9 @@ object OpenGroupAPIV2 {
|
|||||||
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
||||||
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms/$room", isAuthRequired = false)
|
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms/$room", isAuthRequired = false)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val rawRoom = json["room"] as? Map<*, *> ?: throw Error.PARSING_FAILED
|
val rawRoom = json["room"] as? Map<*, *> ?: throw Error.ParsingFailed
|
||||||
val id = rawRoom["id"] as? String ?: throw Error.PARSING_FAILED
|
val id = rawRoom["id"] as? String ?: throw Error.ParsingFailed
|
||||||
val name = rawRoom["name"] as? String ?: throw Error.PARSING_FAILED
|
val name = rawRoom["name"] as? String ?: throw Error.ParsingFailed
|
||||||
val imageID = rawRoom["image_id"] as? String
|
val imageID = rawRoom["image_id"] as? String
|
||||||
Info(id = id, name = name, imageID = imageID)
|
Info(id = id, name = name, imageID = imageID)
|
||||||
}
|
}
|
||||||
@ -481,13 +460,13 @@ object OpenGroupAPIV2 {
|
|||||||
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
||||||
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
|
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val rawRooms = json["rooms"] as? List<Map<*, *>> ?: throw Error.PARSING_FAILED
|
val rawRooms = json["rooms"] as? List<Map<*, *>> ?: throw Error.ParsingFailed
|
||||||
rawRooms.mapNotNull {
|
rawRooms.mapNotNull {
|
||||||
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
|
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
|
||||||
val id = roomJson["id"] as? String ?: return@mapNotNull null
|
val id = roomJson["id"] as? String ?: return@mapNotNull null
|
||||||
val name = roomJson["name"] as? String ?: return@mapNotNull null
|
val name = roomJson["name"] as? String ?: return@mapNotNull null
|
||||||
val imageId = roomJson["image_id"] as? String
|
val imageID = roomJson["image_id"] as? String
|
||||||
Info(id, name, imageId)
|
Info(id, name, imageID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -495,12 +474,11 @@ object OpenGroupAPIV2 {
|
|||||||
fun getMemberCount(room: String, server: String): Promise<Int, Exception> {
|
fun getMemberCount(room: String, server: String): Promise<Int, Exception> {
|
||||||
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
|
||||||
return send(request).map { json ->
|
return send(request).map { json ->
|
||||||
val memberCount = json["member_count"] as? Int ?: throw Error.PARSING_FAILED
|
val memberCount = json["member_count"] as? Int ?: throw Error.ParsingFailed
|
||||||
val storage = MessagingModuleConfiguration.shared.storage
|
val storage = MessagingModuleConfiguration.shared.storage
|
||||||
storage.setUserCount(room, server, memberCount)
|
storage.setUserCount(room, server, memberCount)
|
||||||
memberCount
|
memberCount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
}
|
}
|
@ -9,14 +9,18 @@ import org.session.libsignal.utilities.logging.Log
|
|||||||
import org.whispersystems.curve25519.Curve25519
|
import org.whispersystems.curve25519.Curve25519
|
||||||
|
|
||||||
data class OpenGroupMessageV2(
|
data class OpenGroupMessageV2(
|
||||||
val serverID: Long? = null,
|
val serverID: Long? = null,
|
||||||
val sender: String?,
|
val sender: String?,
|
||||||
val sentTimestamp: Long,
|
val sentTimestamp: Long,
|
||||||
// The serialized protobuf in base64 encoding
|
/**
|
||||||
val base64EncodedData: String,
|
* The serialized protobuf in base64 encoding.
|
||||||
// When sending a message, the sender signs the serialized protobuf with their private key so that
|
*/
|
||||||
// a receiving user can verify that the message wasn't tampered with.
|
val base64EncodedData: String,
|
||||||
val base64EncodedSignature: String? = null
|
/**
|
||||||
|
* When sending a message, the sender signs the serialized protobuf with their private key so that
|
||||||
|
* a receiving user can verify that the message wasn't tampered with.
|
||||||
|
*/
|
||||||
|
val base64EncodedSignature: String? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -28,11 +32,12 @@ data class OpenGroupMessageV2(
|
|||||||
val serverID = json["server_id"] as? Int
|
val serverID = json["server_id"] as? Int
|
||||||
val sender = json["public_key"] as? String
|
val sender = json["public_key"] as? String
|
||||||
val base64EncodedSignature = json["signature"] as? String
|
val base64EncodedSignature = json["signature"] as? String
|
||||||
return OpenGroupMessageV2(serverID = serverID?.toLong(),
|
return OpenGroupMessageV2(
|
||||||
sender = sender,
|
serverID = serverID?.toLong(),
|
||||||
sentTimestamp = sentTimestamp,
|
sender = sender,
|
||||||
base64EncodedData = base64EncodedData,
|
sentTimestamp = sentTimestamp,
|
||||||
base64EncodedSignature = base64EncodedSignature
|
base64EncodedData = base64EncodedData,
|
||||||
|
base64EncodedSignature = base64EncodedSignature
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,29 +46,26 @@ data class OpenGroupMessageV2(
|
|||||||
fun sign(): OpenGroupMessageV2? {
|
fun sign(): OpenGroupMessageV2? {
|
||||||
if (base64EncodedData.isEmpty()) return null
|
if (base64EncodedData.isEmpty()) return null
|
||||||
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair() ?: return null
|
val (publicKey, privateKey) = MessagingModuleConfiguration.shared.storage.getUserKeyPair() ?: return null
|
||||||
|
if (sender != publicKey) return null
|
||||||
if (sender != publicKey) return null // only sign our own messages?
|
|
||||||
|
|
||||||
val signature = try {
|
val signature = try {
|
||||||
curve.calculateSignature(privateKey, decode(base64EncodedData))
|
curve.calculateSignature(privateKey, decode(base64EncodedData))
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("Loki", "Couldn't sign OpenGroupV2Message", e)
|
Log.w("Loki", "Couldn't sign open group message.", e)
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return copy(base64EncodedSignature = Base64.encodeBytes(signature))
|
return copy(base64EncodedSignature = Base64.encodeBytes(signature))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJSON(): Map<String, Any> {
|
fun toJSON(): Map<String, Any> {
|
||||||
val jsonMap = mutableMapOf("data" to base64EncodedData, "timestamp" to sentTimestamp)
|
val json = mutableMapOf( "data" to base64EncodedData, "timestamp" to sentTimestamp )
|
||||||
serverID?.let { jsonMap["server_id"] = serverID }
|
serverID?.let { json["server_id"] = it }
|
||||||
sender?.let { jsonMap["public_key"] = sender }
|
sender?.let { json["public_key"] = it }
|
||||||
base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature }
|
base64EncodedSignature?.let { json["signature"] = it }
|
||||||
return jsonMap
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toProto(): SignalServiceProtos.Content = decode(base64EncodedData).let(PushTransportDetails::getStrippedPaddingMessageBody).let { bytes ->
|
fun toProto(): SignalServiceProtos.Content {
|
||||||
SignalServiceProtos.Content.parseFrom(bytes)
|
val data = decode(base64EncodedData).let(PushTransportDetails::getStrippedPaddingMessageBody)
|
||||||
|
return SignalServiceProtos.Content.parseFrom(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,51 +1,50 @@
|
|||||||
package org.session.libsession.messaging.open_groups
|
package org.session.libsession.messaging.open_groups
|
||||||
|
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
|
import org.session.libsignal.utilities.logging.Log
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class OpenGroupV2(
|
data class OpenGroupV2(
|
||||||
val server: String,
|
val server: String,
|
||||||
val room: String,
|
val room: String,
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val publicKey: String
|
val publicKey: String
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor(server: String, room: String, name: String, publicKey: String) : this(
|
constructor(server: String, room: String, name: String, publicKey: String) : this(
|
||||||
server = server,
|
server = server,
|
||||||
room = room,
|
room = room,
|
||||||
id = "$server.$room",
|
id = "$server.$room",
|
||||||
name = name,
|
name = name,
|
||||||
publicKey = publicKey,
|
publicKey = publicKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun fromJson(jsonAsString: String): OpenGroupV2? {
|
fun fromJSON(jsonAsString: String): OpenGroupV2? {
|
||||||
return try {
|
return try {
|
||||||
val json = JsonUtil.fromJson(jsonAsString)
|
val json = JsonUtil.fromJson(jsonAsString)
|
||||||
if (!json.has("room")) return null
|
if (!json.has("room")) return null
|
||||||
|
val room = json.get("room").asText().toLowerCase(Locale.US)
|
||||||
val room = json.get("room").asText().toLowerCase(Locale.getDefault())
|
val server = json.get("server").asText().toLowerCase(Locale.US)
|
||||||
val server = json.get("server").asText().toLowerCase(Locale.getDefault())
|
|
||||||
val displayName = json.get("displayName").asText()
|
val displayName = json.get("displayName").asText()
|
||||||
val publicKey = json.get("publicKey").asText()
|
val publicKey = json.get("publicKey").asText()
|
||||||
|
|
||||||
OpenGroupV2(server, room, displayName, publicKey)
|
OpenGroupV2(server, room, displayName, publicKey)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e);
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJoinUrl(): String = "$server/$room?public_key=$publicKey"
|
|
||||||
|
|
||||||
fun toJson(): Map<String,String> = mapOf(
|
fun toJson(): Map<String,String> = mapOf(
|
||||||
"room" to room,
|
"room" to room,
|
||||||
"server" to server,
|
"server" to server,
|
||||||
"displayName" to name,
|
"displayName" to name,
|
||||||
"publicKey" to publicKey,
|
"publicKey" to publicKey,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
val joinURL: String get() = "$server/$room?public_key=$publicKey"
|
||||||
}
|
}
|
@ -126,7 +126,7 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
|
|||||||
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closeGroup.publicKey, closeGroup.name, closeGroup.encryptionKeyPair!!, closeGroup.members, closeGroup.admins, message.sentTimestamp!!)
|
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closeGroup.publicKey, closeGroup.name, closeGroup.encryptionKeyPair!!, closeGroup.members, closeGroup.admins, message.sentTimestamp!!)
|
||||||
}
|
}
|
||||||
val allOpenGroups = storage.getAllOpenGroups().map { it.value.server }
|
val allOpenGroups = storage.getAllOpenGroups().map { it.value.server }
|
||||||
val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.toJoinUrl() }
|
val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.joinURL }
|
||||||
for (openGroup in message.openGroups) {
|
for (openGroup in message.openGroups) {
|
||||||
if (allOpenGroups.contains(openGroup) || allV2OpenGroups.contains(openGroup)) continue
|
if (allOpenGroups.contains(openGroup) || allV2OpenGroups.contains(openGroup)) continue
|
||||||
storage.addOpenGroup(openGroup, 1)
|
storage.addOpenGroup(openGroup, 1)
|
||||||
|
Loading…
Reference in New Issue
Block a user