Ensure owned call links are revoked on delete.

This commit is contained in:
Alex Hart
2023-06-13 11:06:05 -03:00
committed by Cody Henthorne
parent 03a212eee4
commit 290b0fe46f
12 changed files with 334 additions and 126 deletions

View File

@@ -6,6 +6,7 @@ import android.database.Cursor
import androidx.core.content.contentValuesOf
import org.signal.core.util.Serializer
import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
@@ -32,7 +33,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.thoughtcrime.securesms.service.webrtc.links.SignalCallLinkState
import org.thoughtcrime.securesms.util.Base64
import java.time.Instant
import java.time.temporal.ChronoUnit
@@ -221,6 +221,64 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
}
}
fun deleteNonAdminCallLinks(roomIds: Set<CallLinkRoomId>) {
val queries = SqlUtil.buildCollectionQuery(ROOM_ID, roomIds)
queries.forEach {
writableDatabase.delete(TABLE_NAME)
.where("${it.where} AND $ADMIN_KEY IS NULL", it.whereArgs)
.run()
}
}
fun getAdminCallLinks(roomIds: Set<CallLinkRoomId>): Set<CallLink> {
val queries = SqlUtil.buildCollectionQuery(ROOM_ID, roomIds)
return queries.map {
writableDatabase
.select()
.from(TABLE_NAME)
.where("${it.where} AND $ADMIN_KEY IS NOT NULL", it.whereArgs)
.run()
.readToList { CallLinkDeserializer.deserialize(it) }
}.flatten().toSet()
}
fun deleteAllNonAdminCallLinksExcept(roomIds: Set<CallLinkRoomId>) {
if (roomIds.isEmpty()) {
writableDatabase.delete(TABLE_NAME)
.where("$ADMIN_KEY IS NULL")
.run()
} else {
SqlUtil.buildCollectionQuery(ROOM_ID, roomIds, collectionOperator = SqlUtil.CollectionOperator.NOT_IN).forEach {
writableDatabase.delete(TABLE_NAME)
.where("${it.where} AND $ADMIN_KEY IS NULL", it.whereArgs)
.run()
}
}
}
fun getAllAdminCallLinksExcept(roomIds: Set<CallLinkRoomId>): Set<CallLink> {
return if (roomIds.isEmpty()) {
writableDatabase
.select()
.from(TABLE_NAME)
.where("$ADMIN_KEY IS NOT NULL")
.run()
.readToList { CallLinkDeserializer.deserialize(it) }
.toSet()
} else {
SqlUtil.buildCollectionQuery(ROOM_ID, roomIds, collectionOperator = SqlUtil.CollectionOperator.NOT_IN).map {
writableDatabase
.select()
.from(TABLE_NAME)
.where("${it.where} AND $ADMIN_KEY IS NOT NULL", it.whereArgs)
.run()
.readToList { CallLinkDeserializer.deserialize(it) }
}.flatten().toSet()
}
}
private fun queryCallLinks(query: String?, offset: Int, limit: Int, asCount: Boolean): Cursor {
//language=sql
val noCallEvent = """
@@ -289,7 +347,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
override fun deserialize(data: Cursor): CallLink {
return CallLink(
recipientId = data.requireLong(RECIPIENT_ID).let { if (it > 0) RecipientId.from(it) else RecipientId.UNKNOWN },
roomId = CallLinkRoomId.fromBytes(Base64.decode(data.requireNonNullString(ROOM_ID))),
roomId = CallLinkRoomId.DatabaseSerializer.deserialize(data.requireNonNullString(ROOM_ID)),
credentials = CallLinkCredentials(
linkKeyBytes = data.requireNonNullBlob(ROOT_KEY),
adminPassBytes = data.requireBlob(ADMIN_KEY)

View File

@@ -33,6 +33,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobs.CallSyncEventJob
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.service.webrtc.links.CallLinkRoomId
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.internal.push.SignalServiceProtos.SyncMessage.CallEvent
import java.util.UUID
@@ -207,6 +208,48 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
.run()
}
fun getCallLinkRoomIdsFromCallRowIds(callRowIds: Set<Long>): Set<CallLinkRoomId> {
return SqlUtil.buildCollectionQuery("$TABLE_NAME.$ID", callRowIds).map { query ->
//language=sql
val statement = """
SELECT ${CallLinkTable.ROOM_ID} FROM $TABLE_NAME
INNER JOIN ${CallLinkTable.TABLE_NAME} ON ${CallLinkTable.TABLE_NAME}.${CallLinkTable.RECIPIENT_ID} = $PEER
WHERE $TYPE = ${Type.serialize(Type.AD_HOC_CALL)} AND ${query.where}
""".toSingleLine()
readableDatabase.query(statement, query.whereArgs).readToList {
CallLinkRoomId.DatabaseSerializer.deserialize(it.requireNonNullString(CallLinkTable.ROOM_ID))
}
}.flatten().toSet()
}
/**
* If a call link has been revoked, or if we do not have a CallLink table entry for an AD_HOC_CALL type
* event, we mark it deleted.
*/
fun updateAdHocCallEventDeletionTimestamps() {
//language=sql
val statement = """
UPDATE $TABLE_NAME
SET $DELETION_TIMESTAMP = ${System.currentTimeMillis()}, $EVENT = ${Event.serialize(Event.DELETE)}
WHERE $TYPE = ${Type.serialize(Type.AD_HOC_CALL)}
AND (
(NOT EXISTS (SELECT 1 FROM ${CallLinkTable.TABLE_NAME} WHERE ${CallLinkTable.RECIPIENT_ID} = $PEER))
OR
(SELECT ${CallLinkTable.REVOKED} FROM ${CallLinkTable.TABLE_NAME} WHERE ${CallLinkTable.RECIPIENT_ID} = $PEER)
)
RETURNING *
""".toSingleLine()
val toSync = writableDatabase.query(statement).readToList {
Call.deserialize(it)
}.toSet()
CallSyncEventJob.enqueueDeleteSyncEvents(toSync)
ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary()
ApplicationDependencies.getDatabaseObserver().notifyCallUpdateObservers()
}
/**
* If a non-ad-hoc call has been deleted from the message database, then we need to
* set its deletion_timestamp to now.
@@ -706,13 +749,13 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
.run()
}
fun deleteCallEvents(callRowIds: Set<Long>) {
fun deleteNonAdHocCallEvents(callRowIds: Set<Long>) {
val messageIds = getMessageIds(callRowIds)
SignalDatabase.messages.deleteCallUpdates(messageIds)
updateCallEventDeletionTimestamps()
}
fun deleteAllCallEventsExcept(callRowIds: Set<Long>, missedOnly: Boolean) {
fun deleteAllNonAdHocCallEventsExcept(callRowIds: Set<Long>, missedOnly: Boolean) {
val callFilter = if (missedOnly) {
"$EVENT = ${Event.serialize(Event.MISSED)} AND $DELETION_TIMESTAMP = 0"
} else {