From 55acd0f0483d17663525695fe0cac75f27473293 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 20 Apr 2022 09:48:44 -0400 Subject: [PATCH] Auto-leave group if added by blocked user. --- .../v2/processing/GroupsV2StateProcessor.java | 13 ++-- .../securesms/jobs/JobManagerFactories.java | 2 + .../securesms/jobs/LeaveGroupV2Job.kt | 55 ++++++++++++++ .../securesms/jobs/LeaveGroupV2WorkerJob.kt | 76 +++++++++++++++++++ .../securesms/SpinnerApplicationContext.kt | 3 +- .../database/TimestampTransformer.kt | 26 +++++++ 6 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2Job.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2WorkerJob.kt create mode 100644 app/src/spinner/java/org/thoughtcrime/securesms/database/TimestampTransformer.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java index eff5dd8bf..16fe62217 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/v2/processing/GroupsV2StateProcessor.java @@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.groups.GroupsV2Authorization; import org.thoughtcrime.securesms.groups.v2.ProfileKeySet; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobs.AvatarGroupsV2DownloadJob; +import org.thoughtcrime.securesms.jobs.LeaveGroupV2Job; import org.thoughtcrime.securesms.jobs.RequestGroupV2InfoJob; import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -111,11 +112,6 @@ public class GroupsV2StateProcessor { } public enum GroupState { - /** - * The message revision was inconsistent with server revision, should ignore - */ - INCONSISTENT, - /** * The local group was successfully updated to be consistent with the message revision */ @@ -590,7 +586,12 @@ public class GroupsV2StateProcessor { Log.i(TAG, String.format("Added as a full member of %s by %s", groupId, addedBy.getId())); - if (addedBy.isSystemContact() || addedBy.isProfileSharing()) { + if (addedBy.isBlocked()) { + Log.i(TAG, "Added by a blocked user. Leaving group."); + ApplicationDependencies.getJobManager().add(new LeaveGroupV2Job(groupId)); + //noinspection UnnecessaryReturnStatement + return; + } else if (addedBy.isSystemContact() || addedBy.isProfileSharing()) { Log.i(TAG, "Group 'adder' is trusted. contact: " + addedBy.isSystemContact() + ", profileSharing: " + addedBy.isProfileSharing()); Log.i(TAG, "Added to a group and auto-enabling profile sharing"); recipientDatabase.setProfileSharing(Recipient.externalGroupExact(context, groupId).getId(), true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 90936c3bf..03ffb2251 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -100,6 +100,8 @@ public final class JobManagerFactories { put(GroupCallPeekWorkerJob.KEY, new GroupCallPeekWorkerJob.Factory()); put(GroupV2UpdateSelfProfileKeyJob.KEY, new GroupV2UpdateSelfProfileKeyJob.Factory()); put(KbsEnclaveMigrationWorkerJob.KEY, new KbsEnclaveMigrationWorkerJob.Factory()); + put(LeaveGroupV2Job.KEY, new LeaveGroupV2Job.Factory()); + put(LeaveGroupV2WorkerJob.KEY, new LeaveGroupV2WorkerJob.Factory()); put(LocalBackupJob.KEY, new LocalBackupJob.Factory()); put(LocalBackupJobApi29.KEY, new LocalBackupJobApi29.Factory()); put(MarkerJob.KEY, new MarkerJob.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2Job.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2Job.kt new file mode 100644 index 000000000..7679cab9c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2Job.kt @@ -0,0 +1,55 @@ +package org.thoughtcrime.securesms.jobs + +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.groups.GroupId +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint + +/** + * During group state processing we sometimes detect situations where we should auto-leave. For example, + * being added to a group by someone we've blocked. This job functions similarly to other GV2 related + * jobs in that it waits for all decryptions to occur and then enqueues the actual [LeaveGroupV2Job] as + * part of the group's message processing queue. + */ +class LeaveGroupV2Job(parameters: Parameters, private val groupId: GroupId.V2) : BaseJob(parameters) { + + constructor(groupId: GroupId.V2) : this( + parameters = Parameters.Builder() + .setQueue("LeaveGroupV2Job") + .addConstraint(DecryptionsDrainedConstraint.KEY) + .setMaxAttempts(Parameters.UNLIMITED) + .build(), + groupId = groupId + ) + + override fun serialize(): Data { + return Data.Builder() + .putString(KEY_GROUP_ID, groupId.toString()) + .build() + } + + override fun getFactoryKey(): String { + return KEY + } + + override fun onRun() { + ApplicationDependencies.getJobManager().add(LeaveGroupV2WorkerJob(groupId)) + } + + override fun onShouldRetry(e: Exception): Boolean = false + + override fun onFailure() = Unit + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): LeaveGroupV2Job { + return LeaveGroupV2Job(parameters, GroupId.parseOrThrow(data.getString(KEY_GROUP_ID)).requireV2()) + } + } + + companion object { + const val KEY = "LeaveGroupV2Job" + + private const val KEY_GROUP_ID = "group_id" + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2WorkerJob.kt b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2WorkerJob.kt new file mode 100644 index 000000000..1894bef09 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupV2WorkerJob.kt @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.jobs + +import org.signal.core.util.logging.Log +import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies +import org.thoughtcrime.securesms.groups.GroupChangeBusyException +import org.thoughtcrime.securesms.groups.GroupChangeFailedException +import org.thoughtcrime.securesms.groups.GroupId +import org.thoughtcrime.securesms.groups.GroupManager +import org.thoughtcrime.securesms.jobmanager.Data +import org.thoughtcrime.securesms.jobmanager.Job +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint +import org.thoughtcrime.securesms.recipients.Recipient +import java.io.IOException + +/** + * Leave a group. See [LeaveGroupV2Job] for more details on how this job should be enqueued. + */ +class LeaveGroupV2WorkerJob(parameters: Parameters, private val groupId: GroupId.V2) : BaseJob(parameters) { + + constructor(groupId: GroupId.V2) : this( + parameters = Parameters.Builder() + .setQueue(PushProcessMessageJob.getQueueName(Recipient.externalGroupExact(ApplicationDependencies.getApplication(), groupId).id)) + .addConstraint(NetworkConstraint.KEY) + .setMaxAttempts(Parameters.UNLIMITED) + .setMaxInstancesForQueue(2) + .build(), + groupId = groupId + ) + + override fun serialize(): Data { + return Data.Builder() + .putString(KEY_GROUP_ID, groupId.toString()) + .build() + } + + override fun getFactoryKey(): String { + return KEY + } + + override fun onRun() { + Log.i(TAG, "Attempting to leave group $groupId") + + val groupRecipient = Recipient.externalGroupExact(ApplicationDependencies.getApplication(), groupId) + + GroupManager.leaveGroup(context, groupId) + + val threadId = SignalDatabase.threads.getThreadIdIfExistsFor(groupRecipient.id) + if (threadId != -1L) { + SignalDatabase.recipients.setProfileSharing(groupRecipient.id, true) + SignalDatabase.threads.setEntireThreadRead(threadId) + SignalDatabase.threads.update(threadId, false, false) + ApplicationDependencies.getMessageNotifier().updateNotification(context) + } + } + + override fun onShouldRetry(e: Exception): Boolean { + return e is GroupChangeBusyException || e is GroupChangeFailedException || e is IOException + } + + override fun onFailure() = Unit + + class Factory : Job.Factory { + override fun create(parameters: Parameters, data: Data): LeaveGroupV2WorkerJob { + return LeaveGroupV2WorkerJob(parameters, GroupId.parseOrThrow(data.getString(KEY_GROUP_ID)).requireV2()) + } + } + + companion object { + const val KEY = "LeaveGroupWorkerJob" + + private val TAG = Log.tag(LeaveGroupV2WorkerJob::class.java) + + private const val KEY_GROUP_ID = "group_id" + } +} diff --git a/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt b/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt index 462a314cc..5ffb59795 100644 --- a/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt +++ b/app/src/spinner/java/org/thoughtcrime/securesms/SpinnerApplicationContext.kt @@ -17,6 +17,7 @@ import org.thoughtcrime.securesms.database.MegaphoneDatabase import org.thoughtcrime.securesms.database.MessageBitmaskColumnTransformer import org.thoughtcrime.securesms.database.QueryMonitor import org.thoughtcrime.securesms.database.SignalDatabase +import org.thoughtcrime.securesms.database.TimestampTransformer import org.thoughtcrime.securesms.keyvalue.SignalStore import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.AppSignatureUtil @@ -40,7 +41,7 @@ class SpinnerApplicationContext : ApplicationContext() { linkedMapOf( "signal" to DatabaseConfig( db = SignalDatabase.rawDatabase, - columnTransformers = listOf(MessageBitmaskColumnTransformer, GV2Transformer, GV2UpdateTransformer, IsStoryTransformer) + columnTransformers = listOf(MessageBitmaskColumnTransformer, GV2Transformer, GV2UpdateTransformer, IsStoryTransformer, TimestampTransformer) ), "jobmanager" to DatabaseConfig(db = JobDatabase.getInstance(this).sqlCipherDatabase), "keyvalue" to DatabaseConfig(db = KeyValueDatabase.getInstance(this).sqlCipherDatabase), diff --git a/app/src/spinner/java/org/thoughtcrime/securesms/database/TimestampTransformer.kt b/app/src/spinner/java/org/thoughtcrime/securesms/database/TimestampTransformer.kt new file mode 100644 index 000000000..91ea68400 --- /dev/null +++ b/app/src/spinner/java/org/thoughtcrime/securesms/database/TimestampTransformer.kt @@ -0,0 +1,26 @@ +package org.thoughtcrime.securesms.database + +import android.database.Cursor +import org.signal.core.util.requireLong +import org.signal.spinner.ColumnTransformer +import org.signal.spinner.DefaultColumnTransformer +import org.thoughtcrime.securesms.util.toLocalDateTime +import org.thoughtcrime.securesms.util.toMillis +import java.time.LocalDateTime + +object TimestampTransformer : ColumnTransformer { + override fun matches(tableName: String?, columnName: String): Boolean { + return columnName.contains("date", true) || + columnName.contains("timestamp", true) + } + + override fun transform(tableName: String?, columnName: String, cursor: Cursor): String { + val timestamp: Long = cursor.requireLong(columnName) + + return if (timestamp > LocalDateTime.of(2000, 1, 1, 0, 0, 0, 0).toMillis()) { + "$timestamp

${timestamp.toLocalDateTime()}" + } else { + DefaultColumnTransformer.transform(tableName, columnName, cursor) + } + } +}