mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-30 13:35:18 +00:00
feat: finishing up OpenGroupAPIV2.kt calls
This commit is contained in:
parent
96e604d06b
commit
aea23a6fc1
@ -314,15 +314,19 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getLastDeletionServerId(room: String, server: String): Long? {
|
override fun getLastDeletionServerId(room: String, server: String): Long? {
|
||||||
TODO("Not yet implemented")
|
return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(room, server)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setLastDeletionServerId(room: String, server: String, newValue: Long) {
|
override fun setLastDeletionServerId(room: String, server: String, newValue: Long) {
|
||||||
TODO("Not yet implemented")
|
DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(room, server, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeLastDeletionServerId(room: String, server: String) {
|
override fun removeLastDeletionServerId(room: String, server: String) {
|
||||||
TODO("Not yet implemented")
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(room, server)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUserCount(room: String, server: String, newValue: Long) {
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).setUserCount(room, server, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
||||||
|
@ -316,7 +316,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
|
|
||||||
fun removeLastMessageServerID(room: String, server:String) {
|
fun removeLastMessageServerID(room: String, server:String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$channel"
|
val index = "$server.$room"
|
||||||
database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,6 +350,12 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index))
|
database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeLastDeletionServerID(room: String, server: String) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
database.delete(lastDeletionServerIDTable, "$lastDeletionServerID = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
fun removeLastDeletionServerID(group: Long, server: String) {
|
fun removeLastDeletionServerID(group: Long, server: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -379,7 +385,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index))
|
database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setUserCount(room: String, server: String, newValue: Int) {
|
override fun setUserCount(room: String, server: String, newValue: Long) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$room"
|
val index = "$server.$room"
|
||||||
val row = wrap(mapOf( publicChatID to index, userCount to newValue.toString() ))
|
val row = wrap(mapOf( publicChatID to index, userCount to newValue.toString() ))
|
||||||
|
@ -78,6 +78,7 @@ interface StorageProtocol {
|
|||||||
|
|
||||||
fun updateTitle(groupID: String, newValue: String)
|
fun updateTitle(groupID: String, newValue: String)
|
||||||
fun updateProfilePicture(groupID: String, newValue: ByteArray)
|
fun updateProfilePicture(groupID: String, newValue: ByteArray)
|
||||||
|
fun setUserCount(room: String, server: String, newValue: Long)
|
||||||
|
|
||||||
// Last Message Server ID
|
// Last Message Server ID
|
||||||
fun getLastMessageServerId(room: String, server: String): Long?
|
fun getLastMessageServerId(room: String, server: String): Long?
|
||||||
|
@ -1,19 +1,36 @@
|
|||||||
package org.session.libsession.messaging.opengroups
|
package org.session.libsession.messaging.opengroups
|
||||||
|
|
||||||
|
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 okhttp3.HttpUrl
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.MediaType
|
||||||
|
import okhttp3.RequestBody
|
||||||
import org.session.libsession.messaging.MessagingConfiguration
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error
|
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error
|
||||||
|
import org.session.libsession.snode.OnionRequestAPI
|
||||||
|
import org.session.libsession.utilities.AESGCM
|
||||||
import org.session.libsignal.service.loki.api.utilities.HTTP
|
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.utilities.removing05PrefixIfNeeded
|
||||||
|
import org.session.libsignal.service.loki.utilities.toHexString
|
||||||
|
import org.session.libsignal.utilities.Base64.*
|
||||||
|
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.*
|
import java.util.*
|
||||||
|
|
||||||
object OpenGroupAPIV2 {
|
object OpenGroupAPIV2 {
|
||||||
|
|
||||||
private val moderators: HashMap<String, 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 = "https://sessionopengroup.com"
|
const val DEFAULT_SERVER = "https://sessionopengroup.com"
|
||||||
const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b"
|
const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b"
|
||||||
|
|
||||||
|
private val sharedContext = Kovenant.createContext()
|
||||||
|
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
||||||
|
|
||||||
sealed class Error : Exception() {
|
sealed class Error : Exception() {
|
||||||
object GENERIC : Error()
|
object GENERIC : Error()
|
||||||
object PARSING_FAILED : Error()
|
object PARSING_FAILED : Error()
|
||||||
@ -26,7 +43,7 @@ object OpenGroupAPIV2 {
|
|||||||
data class Info(
|
data class Info(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val imageID: String
|
val imageID: String?
|
||||||
)
|
)
|
||||||
|
|
||||||
data class Request(
|
data class Request(
|
||||||
@ -34,30 +51,67 @@ object OpenGroupAPIV2 {
|
|||||||
val room: String?,
|
val room: String?,
|
||||||
val server: String,
|
val server: String,
|
||||||
val endpoint: String,
|
val endpoint: String,
|
||||||
val queryParameters: Map<String, String>,
|
val queryParameters: Map<String, String> = mapOf(),
|
||||||
val parameters: Any,
|
val parameters: Any? = null,
|
||||||
val headers: Map<String, String>,
|
val headers: Map<String, String> = mapOf(),
|
||||||
val isAuthRequired: Boolean,
|
val isAuthRequired: Boolean = true,
|
||||||
// Always `true` under normal circumstances. You might want to disable
|
// Always `true` under normal circumstances. You might want to disable
|
||||||
// this when running over Lokinet.
|
// this when running over Lokinet.
|
||||||
val useOnionRouting: Boolean
|
val useOnionRouting: Boolean = true
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun send(request: Request): Promise<Any, Exception> {
|
private fun createBody(parameters: Any): RequestBody {
|
||||||
|
val parametersAsJSON = JsonUtil.toJson(parameters)
|
||||||
|
return RequestBody.create(MediaType.get("application/json"), parametersAsJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun send(request: Request): Promise<Map<*,*>, Exception> {
|
||||||
val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL)
|
val parsed = HttpUrl.parse(request.server) ?: return Promise.ofFail(Error.INVALID_URL)
|
||||||
val urlBuilder = HttpUrl.Builder()
|
val urlBuilder = HttpUrl.Builder()
|
||||||
.scheme(parsed.scheme())
|
.scheme(parsed.scheme())
|
||||||
.host(parsed.host())
|
.host(parsed.host())
|
||||||
.addPathSegment(request.endpoint)
|
.addPathSegment(request.endpoint)
|
||||||
|
|
||||||
for ((key, value) in request.queryParameters) {
|
if (request.verb == GET) {
|
||||||
urlBuilder.addQueryParameter(key, value)
|
for ((key, value) in request.queryParameters) {
|
||||||
|
urlBuilder.addQueryParameter(key, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun execute(token: String?): Promise<Map<*, *>, Exception> {
|
fun execute(token: String?): Promise<Map<*, *>, Exception> {
|
||||||
|
val requestBuilder = okhttp3.Request.Builder()
|
||||||
|
.url(urlBuilder.build())
|
||||||
|
if (request.isAuthRequired) {
|
||||||
|
if (token.isNullOrEmpty()) throw IllegalStateException("No auth token for request")
|
||||||
|
requestBuilder.addHeader("Authorization", token)
|
||||||
|
}
|
||||||
|
when (request.verb) {
|
||||||
|
GET -> requestBuilder.get()
|
||||||
|
PUT -> requestBuilder.put(createBody(request.parameters!!))
|
||||||
|
POST -> requestBuilder.post(createBody(request.parameters!!))
|
||||||
|
DELETE -> requestBuilder.delete(createBody(request.parameters!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!request.room.isNullOrEmpty()) {
|
||||||
|
requestBuilder.header("Room", request.room)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (request.useOnionRouting) {
|
||||||
|
val publicKey = MessagingConfiguration.shared.storage.getOpenGroupPublicKey(request.server)
|
||||||
|
?: return Promise.ofFail(Error.NO_PUBLIC_KEY)
|
||||||
|
return OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey)
|
||||||
|
.fail { e ->
|
||||||
|
if (e is OnionRequestAPI.HTTPRequestFailedAtDestinationException
|
||||||
|
&& e.statusCode == 401) {
|
||||||
|
MessagingConfiguration.shared.storage.removeAuthToken(request.server)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests."))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return if (request.isAuthRequired) {
|
return if (request.isAuthRequired) {
|
||||||
getAuthToken(request.room!!, request.server).bind(::execute)
|
getAuthToken(request.room!!, request.server).bind(sharedContext) { execute(it) }
|
||||||
} else {
|
} else {
|
||||||
execute(null)
|
execute(null)
|
||||||
}
|
}
|
||||||
@ -69,7 +123,7 @@ object OpenGroupAPIV2 {
|
|||||||
Promise.of(it)
|
Promise.of(it)
|
||||||
} ?: run {
|
} ?: run {
|
||||||
requestNewAuthToken(room, server)
|
requestNewAuthToken(room, server)
|
||||||
.bind { claimAuthToken(it, room, server) }
|
.bind(sharedContext) { claimAuthToken(it, room, server) }
|
||||||
.success { authToken ->
|
.success { authToken ->
|
||||||
storage.setAuthToken(room, server, authToken)
|
storage.setAuthToken(room, server, authToken)
|
||||||
}
|
}
|
||||||
@ -77,75 +131,204 @@ object OpenGroupAPIV2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
fun requestNewAuthToken(room: String, server: String): Promise<String, Exception> {
|
||||||
val (publicKey, _) = MessagingConfiguration.shared.storage.getUserKeyPair()
|
val (publicKey, privateKey) = MessagingConfiguration.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)
|
||||||
|
return send(request).map(sharedContext) { json ->
|
||||||
|
val challenge = json["challenge"] as? Map<*,*> ?: throw Error.PARSING_FAILED
|
||||||
|
val base64EncodedCiphertext = challenge["ciphertext"] as? String ?: throw Error.PARSING_FAILED
|
||||||
|
val base64EncodedEphemeralPublicKey = challenge["ephemeral_public_key"] as? String ?: throw Error.PARSING_FAILED
|
||||||
|
val ciphertext = decode(base64EncodedCiphertext)
|
||||||
|
val ephemeralPublicKey = decode(base64EncodedEphemeralPublicKey)
|
||||||
|
val symmetricKey = AESGCM.generateSymmetricKey(ephemeralPublicKey, privateKey)
|
||||||
|
val tokenAsData = try {
|
||||||
|
AESGCM.decrypt(ciphertext, symmetricKey)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw Error.DECRYPTION_FAILED
|
||||||
|
}
|
||||||
|
tokenAsData.toHexString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
|
fun claimAuthToken(authToken: String, room: String, server: String): Promise<String, Exception> {
|
||||||
TODO("implement")
|
val parameters = mapOf("public_key" to MessagingConfiguration.shared.storage.getUserPublicKey()!!)
|
||||||
|
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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteAuthToken(room: String, server: String): Promise<Long, Exception> {
|
fun deleteAuthToken(room: String, server: String): Promise<Unit, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "auth_token")
|
||||||
|
return send(request).map(sharedContext) {
|
||||||
|
MessagingConfiguration.shared.storage.removeAuthToken(room, server)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region Sending
|
||||||
fun upload(file: ByteArray, room: String, server: String): Promise<Long, Exception> {
|
fun upload(file: ByteArray, room: String, server: String): Promise<Long, Exception> {
|
||||||
TODO("implement")
|
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 ->
|
||||||
|
json["result"] as? Long ?: throw Error.PARSING_FAILED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
fun download(file: Long, room: String, server: String): Promise<ByteArray, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "files/$file")
|
||||||
|
return send(request).map(sharedContext) { json ->
|
||||||
|
val base64EncodedFile = json["result"] as? String ?: throw Error.PARSING_FAILED
|
||||||
|
decode(base64EncodedFile) ?: throw Error.PARSING_FAILED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
fun send(message: OpenGroupMessageV2, room: String, server: String): Promise<OpenGroupMessageV2, Exception> {
|
||||||
TODO("implement")
|
val signedMessage = message.sign() ?: return Promise.ofFail(Error.SIGNING_FAILED)
|
||||||
|
val json = signedMessage.toJSON()
|
||||||
|
val request = Request(verb = POST, room = room, server = server, endpoint = "messages", parameters = json)
|
||||||
|
return send(request).map(sharedContext) {
|
||||||
|
@Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map<String,String> ?: throw Error.PARSING_FAILED
|
||||||
|
OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.PARSING_FAILED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Messages
|
||||||
fun getMessages(room: String, server: String): Promise<List<OpenGroupMessageV2>, Exception> {
|
fun getMessages(room: String, server: String): Promise<List<OpenGroupMessageV2>, Exception> {
|
||||||
TODO("implement")
|
val storage = MessagingConfiguration.shared.storage
|
||||||
}
|
val queryParameters = mutableMapOf<String,String>()
|
||||||
|
storage.getLastMessageServerId(room,server)?.let { lastId ->
|
||||||
|
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 ->
|
||||||
|
@Suppress("UNCHECKED_CAST") val rawMessages = jsonList["messages"] as? List<Map<String,Any>> ?: throw Error.PARSING_FAILED
|
||||||
|
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
||||||
|
|
||||||
|
var currentMax = lastMessageServerId
|
||||||
|
val messages = rawMessages.mapNotNull { json ->
|
||||||
|
val message = OpenGroupMessageV2.fromJSON(json) ?: return@mapNotNull null
|
||||||
|
if (message.serverID == null || message.sender.isNullOrEmpty()) return@mapNotNull null
|
||||||
|
val sender = message.sender
|
||||||
|
val data = decode(message.base64EncodedData)
|
||||||
|
val signature = decode(message.base64EncodedSignature)
|
||||||
|
val publicKey = sender.removing05PrefixIfNeeded().encodeToByteArray()
|
||||||
|
val isValid = curve.verifySignature(publicKey, data, signature)
|
||||||
|
if (!isValid) {
|
||||||
|
Log.d("Loki", "Ignoring message with invalid signature")
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
if (message.serverID > lastMessageServerId) {
|
||||||
|
currentMax = message.serverID
|
||||||
|
}
|
||||||
|
message
|
||||||
|
}
|
||||||
|
storage.setLastMessageServerId(room,server,currentMax)
|
||||||
|
messages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Message Deletion
|
||||||
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
fun deleteMessage(serverID: Long, room: String, server: String): Promise<Unit, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "message/$serverID")
|
||||||
|
return send(request).map(sharedContext) {
|
||||||
|
Log.d("Loki", "Deleted server message")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDeletedMessages(room: String, server: String): Promise<List<Long>, Exception> {
|
fun getDeletedMessages(room: String, server: String): Promise<List<Long>, Exception> {
|
||||||
TODO("implement")
|
val storage = MessagingConfiguration.shared.storage
|
||||||
|
val queryParameters = mutableMapOf<String,String>()
|
||||||
|
storage.getLastDeletionServerId(room, server)?.let { last ->
|
||||||
|
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 ->
|
||||||
|
@Suppress("UNCHECKED_CAST") val serverIDs = json["ids"] as? List<Long> ?: throw Error.PARSING_FAILED
|
||||||
|
val lastMessageServerId = storage.getLastMessageServerId(room, server) ?: 0
|
||||||
|
val serverID = serverIDs.maxOrNull() ?: 0
|
||||||
|
if (serverID > lastMessageServerId) {
|
||||||
|
storage.setLastDeletionServerId(room, server, serverID)
|
||||||
|
}
|
||||||
|
serverIDs
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Moderation
|
||||||
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
|
fun getModerators(room: String, server: String): Promise<List<String>, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "moderators")
|
||||||
|
return send(request).map(sharedContext) { json ->
|
||||||
|
@Suppress("UNCHECKED_CAST") val moderatorsJson = json["moderators"] as? List<String> ?: throw Error.PARSING_FAILED
|
||||||
|
val id = "$server.$room"
|
||||||
|
moderators[id] = moderatorsJson.toMutableSet()
|
||||||
|
moderatorsJson
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
fun ban(publicKey: String, room: String, server: String): Promise<Unit, Exception> {
|
||||||
TODO("implement")
|
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) {
|
||||||
|
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> {
|
||||||
TODO("implement")
|
val request = Request(verb = DELETE, room = room, server = server, endpoint = "block_list/$publicKey")
|
||||||
|
return send(request).map(sharedContext) {
|
||||||
|
Log.d("Loki", "Unbanned user $publicKey from $server.$room")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isUserModerator(publicKey: String, room: String, server: String): Promise<Boolean, Exception> {
|
fun isUserModerator(publicKey: String, room: String, server: String): Boolean = moderators["$server.$room"]?.contains(publicKey) ?: false
|
||||||
TODO("implement")
|
// endregion
|
||||||
}
|
|
||||||
|
|
||||||
fun getDefaultRoomsIfNeeded() {
|
// region General
|
||||||
TODO("implement")
|
fun getDefaultRoomsIfNeeded(): Promise<List<Info>, Exception> {
|
||||||
|
val storage = MessagingConfiguration.shared.storage
|
||||||
|
storage.setOpenGroupPublicKey(DEFAULT_SERVER, DEFAULT_SERVER_PUBLIC_KEY)
|
||||||
|
return getAllRooms(DEFAULT_SERVER)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
fun getInfo(room: String, server: String): Promise<Info, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "rooms/$room", isAuthRequired = false)
|
||||||
|
return send(request).map(sharedContext) { 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
|
||||||
|
val imageID = rawRoom["image_id"] as? String
|
||||||
|
Info(id = id, name = name, imageID = imageID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
fun getAllRooms(server: String): Promise<List<Info>, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = GET, room = null, server = server, endpoint = "rooms", isAuthRequired = false)
|
||||||
|
return send(request).map(sharedContext) { json ->
|
||||||
|
val rawRooms = json["rooms"] as? Map<*,*> ?: throw Error.PARSING_FAILED
|
||||||
|
rawRooms.mapNotNull {
|
||||||
|
val roomJson = it as? Map<*, *> ?: return@mapNotNull null
|
||||||
|
val id = roomJson["id"] as? String ?: return@mapNotNull null
|
||||||
|
val name = roomJson["name"] as? String ?: return@mapNotNull null
|
||||||
|
val imageId = roomJson["image_id"] as? String
|
||||||
|
Info(id, name, imageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
|
fun getMemberCount(room: String, server: String): Promise<Long, Exception> {
|
||||||
TODO("implement")
|
val request = Request(verb = GET, room = room, server = server, endpoint = "member_count")
|
||||||
|
return send(request).map(sharedContext) { json ->
|
||||||
|
val memberCount = json["member_count"] as? Long ?: throw Error.PARSING_FAILED
|
||||||
|
val storage = MessagingConfiguration.shared.storage
|
||||||
|
storage.setUserCount(room, server, memberCount)
|
||||||
|
memberCount
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,21 @@ data class OpenGroupMessageV2(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
private val curve = Curve25519.getInstance(Curve25519.BEST)
|
||||||
|
|
||||||
|
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
|
||||||
|
val base64EncodedData = json["data"] as? String ?: return null
|
||||||
|
val sentTimestamp = json["timestamp"] as? Long ?: return null
|
||||||
|
val serverID = json["server_id"] as? Long
|
||||||
|
val sender = json["public_key"] as? String
|
||||||
|
val base64EncodedSignature = json["signature"] as? String
|
||||||
|
return OpenGroupMessageV2(serverID = serverID,
|
||||||
|
sender = sender,
|
||||||
|
sentTimestamp = sentTimestamp,
|
||||||
|
base64EncodedData = base64EncodedData,
|
||||||
|
base64EncodedSignature = base64EncodedSignature
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sign(): OpenGroupMessageV2? {
|
fun sign(): OpenGroupMessageV2? {
|
||||||
@ -43,20 +58,4 @@ data class OpenGroupMessageV2(
|
|||||||
base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature }
|
base64EncodedSignature?.let { jsonMap["signature"] = base64EncodedSignature }
|
||||||
return jsonMap
|
return jsonMap
|
||||||
}
|
}
|
||||||
|
|
||||||
fun fromJSON(json: Map<String, Any>): OpenGroupMessageV2? {
|
|
||||||
val base64EncodedData = json["data"] as? String ?: return null
|
|
||||||
val sentTimestamp = json["timestamp"] as? Long ?: return null
|
|
||||||
val serverID = json["server_id"] as? Long
|
|
||||||
val sender = json["public_key"] as? String
|
|
||||||
val base64EncodedSignature = json["signature"] as? String
|
|
||||||
return OpenGroupMessageV2(serverID = serverID,
|
|
||||||
sender = sender,
|
|
||||||
sentTimestamp = sentTimestamp,
|
|
||||||
base64EncodedData = base64EncodedData,
|
|
||||||
base64EncodedSignature = base64EncodedSignature
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -1,14 +1,16 @@
|
|||||||
package org.session.libsession.utilities
|
package org.session.libsession.utilities
|
||||||
|
|
||||||
import org.whispersystems.curve25519.Curve25519
|
import androidx.annotation.WorkerThread
|
||||||
import org.session.libsignal.libsignal.util.ByteUtil
|
import org.session.libsignal.libsignal.util.ByteUtil
|
||||||
import org.session.libsignal.service.internal.util.Util
|
import org.session.libsignal.service.internal.util.Util
|
||||||
import org.session.libsignal.utilities.Hex
|
import org.session.libsignal.utilities.Hex
|
||||||
|
import org.whispersystems.curve25519.Curve25519
|
||||||
import javax.crypto.Cipher
|
import javax.crypto.Cipher
|
||||||
import javax.crypto.Mac
|
import javax.crypto.Mac
|
||||||
import javax.crypto.spec.GCMParameterSpec
|
import javax.crypto.spec.GCMParameterSpec
|
||||||
import javax.crypto.spec.SecretKeySpec
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
internal object AESGCM {
|
internal object AESGCM {
|
||||||
|
|
||||||
internal data class EncryptionResult(
|
internal data class EncryptionResult(
|
||||||
@ -31,6 +33,16 @@ internal object AESGCM {
|
|||||||
return cipher.doFinal(ciphertext)
|
return cipher.doFinal(ciphertext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync. Don't call from the main thread.
|
||||||
|
*/
|
||||||
|
internal fun generateSymmetricKey(x25519PublicKey: ByteArray, x25519PrivateKey: ByteArray): ByteArray {
|
||||||
|
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, x25519PrivateKey)
|
||||||
|
val mac = Mac.getInstance("HmacSHA256")
|
||||||
|
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
||||||
|
return mac.doFinal(ephemeralSharedSecret)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sync. Don't call from the main thread.
|
* Sync. Don't call from the main thread.
|
||||||
*/
|
*/
|
||||||
@ -47,10 +59,7 @@ internal object AESGCM {
|
|||||||
internal fun encrypt(plaintext: ByteArray, hexEncodedX25519PublicKey: String): EncryptionResult {
|
internal fun encrypt(plaintext: ByteArray, hexEncodedX25519PublicKey: String): EncryptionResult {
|
||||||
val x25519PublicKey = Hex.fromStringCondensed(hexEncodedX25519PublicKey)
|
val x25519PublicKey = Hex.fromStringCondensed(hexEncodedX25519PublicKey)
|
||||||
val ephemeralKeyPair = Curve25519.getInstance(Curve25519.BEST).generateKeyPair()
|
val ephemeralKeyPair = Curve25519.getInstance(Curve25519.BEST).generateKeyPair()
|
||||||
val ephemeralSharedSecret = Curve25519.getInstance(Curve25519.BEST).calculateAgreement(x25519PublicKey, ephemeralKeyPair.privateKey)
|
val symmetricKey = generateSymmetricKey(x25519PublicKey, ephemeralKeyPair.privateKey)
|
||||||
val mac = Mac.getInstance("HmacSHA256")
|
|
||||||
mac.init(SecretKeySpec("LOKI".toByteArray(), "HmacSHA256"))
|
|
||||||
val symmetricKey = mac.doFinal(ephemeralSharedSecret)
|
|
||||||
val ciphertext = encrypt(plaintext, symmetricKey)
|
val ciphertext = encrypt(plaintext, symmetricKey)
|
||||||
return EncryptionResult(ciphertext, symmetricKey, ephemeralKeyPair.publicKey)
|
return EncryptionResult(ciphertext, symmetricKey, ephemeralKeyPair.publicKey)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ interface LokiAPIDatabaseProtocol {
|
|||||||
fun setLastMessageServerID(room: String, server: String, newValue: Long)
|
fun setLastMessageServerID(room: String, server: String, newValue: Long)
|
||||||
fun getLastDeletionServerID(room: String, server: String): Long?
|
fun getLastDeletionServerID(room: String, server: String): Long?
|
||||||
fun setLastDeletionServerID(room: String, server: String, newValue: Long)
|
fun setLastDeletionServerID(room: String, server: String, newValue: Long)
|
||||||
fun setUserCount(room: String, server: String, newValue: Int)
|
fun setUserCount(room: String, server: String, newValue: Long)
|
||||||
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
||||||
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
||||||
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
||||||
|
Loading…
Reference in New Issue
Block a user