Write previous group state to the database for advanced change messages.
parent
b40fd7b243
commit
38fa58c0a3
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database.model;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import com.google.protobuf.ByteString;
|
import com.google.protobuf.ByteString;
|
||||||
|
@ -81,7 +82,11 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<UpdateDescription> describeChanges(@NonNull DecryptedGroupChange change) {
|
List<UpdateDescription> describeChanges(@Nullable DecryptedGroup previousGroupState, @NonNull DecryptedGroupChange change) {
|
||||||
|
if (DecryptedGroup.getDefaultInstance().equals(previousGroupState)) {
|
||||||
|
previousGroupState = null;
|
||||||
|
}
|
||||||
|
|
||||||
List<UpdateDescription> updates = new LinkedList<>();
|
List<UpdateDescription> updates = new LinkedList<>();
|
||||||
|
|
||||||
if (change.getEditor().isEmpty() || UuidUtil.UNKNOWN_UUID.equals(UuidUtil.fromByteString(change.getEditor()))) {
|
if (change.getEditor().isEmpty() || UuidUtil.UNKNOWN_UUID.equals(UuidUtil.fromByteString(change.getEditor()))) {
|
||||||
|
@ -96,7 +101,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
describeUnknownEditorNewTimer(change, updates);
|
describeUnknownEditorNewTimer(change, updates);
|
||||||
describeUnknownEditorNewAttributeAccess(change, updates);
|
describeUnknownEditorNewAttributeAccess(change, updates);
|
||||||
describeUnknownEditorNewMembershipAccess(change, updates);
|
describeUnknownEditorNewMembershipAccess(change, updates);
|
||||||
describeUnknownEditorNewGroupInviteLinkAccess(change, updates);
|
describeUnknownEditorNewGroupInviteLinkAccess(previousGroupState, change, updates);
|
||||||
describeRequestingMembers(change, updates);
|
describeRequestingMembers(change, updates);
|
||||||
describeUnknownEditorRequestingMembersApprovals(change, updates);
|
describeUnknownEditorRequestingMembersApprovals(change, updates);
|
||||||
describeUnknownEditorRequestingMembersDeletes(change, updates);
|
describeUnknownEditorRequestingMembersDeletes(change, updates);
|
||||||
|
@ -119,7 +124,7 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
describeNewTimer(change, updates);
|
describeNewTimer(change, updates);
|
||||||
describeNewAttributeAccess(change, updates);
|
describeNewAttributeAccess(change, updates);
|
||||||
describeNewMembershipAccess(change, updates);
|
describeNewMembershipAccess(change, updates);
|
||||||
describeNewGroupInviteLinkAccess(change, updates);
|
describeNewGroupInviteLinkAccess(previousGroupState, change, updates);
|
||||||
describeRequestingMembers(change, updates);
|
describeRequestingMembers(change, updates);
|
||||||
describeRequestingMembersApprovals(change, updates);
|
describeRequestingMembersApprovals(change, updates);
|
||||||
describeRequestingMembersDeletes(change, updates);
|
describeRequestingMembersDeletes(change, updates);
|
||||||
|
@ -509,7 +514,16 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeNewGroupInviteLinkAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
private void describeNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState,
|
||||||
|
@NonNull DecryptedGroupChange change,
|
||||||
|
@NonNull List<UpdateDescription> updates)
|
||||||
|
{
|
||||||
|
AccessControl.AccessRequired previousAccessControl = null;
|
||||||
|
|
||||||
|
if (previousGroupState != null) {
|
||||||
|
previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink();
|
||||||
|
}
|
||||||
|
|
||||||
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
boolean editorIsYou = change.getEditor().equals(selfUuidBytes);
|
||||||
boolean groupLinkEnabled = false;
|
boolean groupLinkEnabled = false;
|
||||||
|
|
||||||
|
@ -517,17 +531,33 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
case ANY:
|
case ANY:
|
||||||
groupLinkEnabled = true;
|
groupLinkEnabled = true;
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off)));
|
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_off_admin_approval_for_the_group_link)));
|
||||||
|
} else {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_off)));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, editor)));
|
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
||||||
|
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_off_admin_approval_for_the_group_link, editor)));
|
||||||
|
} else {
|
||||||
|
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_off, editor)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ADMINISTRATOR:
|
case ADMINISTRATOR:
|
||||||
groupLinkEnabled = true;
|
groupLinkEnabled = true;
|
||||||
if (editorIsYou) {
|
if (editorIsYou) {
|
||||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on)));
|
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_admin_approval_for_the_group_link)));
|
||||||
|
} else {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_you_turned_on_the_group_link_with_admin_approval_on)));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, editor)));
|
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
||||||
|
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_admin_approval_for_the_group_link, editor)));
|
||||||
|
} else {
|
||||||
|
updates.add(updateDescription(change.getEditor(), editor -> context.getString(R.string.MessageRecord_s_turned_on_the_group_link_with_admin_approval_on, editor)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case UNSATISFIABLE:
|
case UNSATISFIABLE:
|
||||||
|
@ -548,13 +578,30 @@ final class GroupsV2UpdateMessageProducer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void describeUnknownEditorNewGroupInviteLinkAccess(@NonNull DecryptedGroupChange change, @NonNull List<UpdateDescription> updates) {
|
private void describeUnknownEditorNewGroupInviteLinkAccess(@Nullable DecryptedGroup previousGroupState,
|
||||||
|
@NonNull DecryptedGroupChange change,
|
||||||
|
@NonNull List<UpdateDescription> updates)
|
||||||
|
{
|
||||||
|
AccessControl.AccessRequired previousAccessControl = null;
|
||||||
|
|
||||||
|
if (previousGroupState != null) {
|
||||||
|
previousAccessControl = previousGroupState.getAccessControl().getAddFromInviteLink();
|
||||||
|
}
|
||||||
|
|
||||||
switch (change.getNewInviteLinkAccess()) {
|
switch (change.getNewInviteLinkAccess()) {
|
||||||
case ANY:
|
case ANY:
|
||||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off)));
|
if (previousAccessControl == AccessControl.AccessRequired.ADMINISTRATOR) {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off)));
|
||||||
|
} else {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off)));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ADMINISTRATOR:
|
case ADMINISTRATOR:
|
||||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on)));
|
if (previousAccessControl == AccessControl.AccessRequired.ANY) {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on)));
|
||||||
|
} else {
|
||||||
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on)));
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case UNSATISFIABLE:
|
case UNSATISFIABLE:
|
||||||
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off)));
|
updates.add(updateDescription(context.getString(R.string.MessageRecord_the_group_link_has_been_turned_off)));
|
||||||
|
|
|
@ -177,7 +177,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||||
GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, descriptionStrategy, Recipient.self().getUuid().get());
|
GroupsV2UpdateMessageProducer updateMessageProducer = new GroupsV2UpdateMessageProducer(context, descriptionStrategy, Recipient.self().getUuid().get());
|
||||||
|
|
||||||
if (decryptedGroupV2Context.hasChange() && decryptedGroupV2Context.getGroupState().getRevision() != 0) {
|
if (decryptedGroupV2Context.hasChange() && decryptedGroupV2Context.getGroupState().getRevision() != 0) {
|
||||||
return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getChange()));
|
return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getPreviousGroupState(), decryptedGroupV2Context.getChange()));
|
||||||
} else {
|
} else {
|
||||||
return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState(), decryptedGroupV2Context.getChange());
|
return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState(), decryptedGroupV2Context.getChange());
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,7 +193,7 @@ final class GroupManagerV2 {
|
||||||
.setEditor(UuidUtil.toByteString(selfUuid))
|
.setEditor(UuidUtil.toByteString(selfUuid))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, decryptedGroup, groupChange, null);
|
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, new GroupMutation(null, groupChange, decryptedGroup), null);
|
||||||
|
|
||||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient,
|
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient,
|
||||||
recipientAndThread.threadId,
|
recipientAndThread.threadId,
|
||||||
|
@ -505,16 +505,18 @@ final class GroupManagerV2 {
|
||||||
private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change)
|
private GroupManager.GroupActionResult commitChange(@NonNull GroupChange.Actions.Builder change)
|
||||||
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
|
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
|
||||||
{
|
{
|
||||||
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
|
final GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
|
||||||
final GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
|
final GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
|
||||||
final int nextRevision = v2GroupProperties.getGroupRevision() + 1;
|
final int nextRevision = v2GroupProperties.getGroupRevision() + 1;
|
||||||
final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
|
final GroupChange.Actions changeActions = change.setRevision(nextRevision).build();
|
||||||
final DecryptedGroupChange decryptedChange;
|
final DecryptedGroupChange decryptedChange;
|
||||||
final DecryptedGroup decryptedGroupState;
|
final DecryptedGroup decryptedGroupState;
|
||||||
|
final DecryptedGroup previousGroupState;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
previousGroupState = v2GroupProperties.getDecryptedGroup();
|
||||||
decryptedChange = groupOperations.decryptChange(changeActions, selfUuid);
|
decryptedChange = groupOperations.decryptChange(changeActions, selfUuid);
|
||||||
decryptedGroupState = DecryptedGroupUtil.apply(v2GroupProperties.getDecryptedGroup(), decryptedChange);
|
decryptedGroupState = DecryptedGroupUtil.apply(previousGroupState, decryptedChange);
|
||||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||||
Log.w(TAG, e);
|
Log.w(TAG, e);
|
||||||
throw new IOException(e);
|
throw new IOException(e);
|
||||||
|
@ -523,7 +525,8 @@ final class GroupManagerV2 {
|
||||||
GroupChange signedGroupChange = commitToServer(changeActions);
|
GroupChange signedGroupChange = commitToServer(changeActions);
|
||||||
groupDatabase.update(groupId, decryptedGroupState);
|
groupDatabase.update(groupId, decryptedGroupState);
|
||||||
|
|
||||||
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange);
|
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
|
||||||
|
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange);
|
||||||
int newMembersCount = decryptedChange.getNewMembersCount();
|
int newMembersCount = decryptedChange.getNewMembersCount();
|
||||||
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList());
|
List<RecipientId> newPendingMembers = getPendingMemberRecipientIds(decryptedChange.getNewPendingMembersList());
|
||||||
|
|
||||||
|
@ -681,7 +684,7 @@ final class GroupManagerV2 {
|
||||||
} else if (requestToJoin) {
|
} else if (requestToJoin) {
|
||||||
Log.i(TAG, "Requested to join, cannot send update");
|
Log.i(TAG, "Requested to join, cannot send update");
|
||||||
|
|
||||||
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange);
|
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange);
|
||||||
|
|
||||||
return new GroupManager.GroupActionResult(groupRecipient,
|
return new GroupManager.GroupActionResult(groupRecipient,
|
||||||
recipientAndThread.threadId,
|
recipientAndThread.threadId,
|
||||||
|
@ -706,7 +709,7 @@ final class GroupManagerV2 {
|
||||||
System.currentTimeMillis(),
|
System.currentTimeMillis(),
|
||||||
decryptedChange);
|
decryptedChange);
|
||||||
|
|
||||||
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange);
|
RecipientAndThread recipientAndThread = sendGroupUpdate(groupMasterKey, new GroupMutation(null, decryptedChange, decryptedGroup), signedGroupChange);
|
||||||
|
|
||||||
return new GroupManager.GroupActionResult(groupRecipient,
|
return new GroupManager.GroupActionResult(groupRecipient,
|
||||||
recipientAndThread.threadId,
|
recipientAndThread.threadId,
|
||||||
|
@ -905,7 +908,7 @@ final class GroupManagerV2 {
|
||||||
|
|
||||||
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.getRevision()));
|
groupDatabase.update(groupId, resetRevision(newGroup, decryptedGroup.getRevision()));
|
||||||
|
|
||||||
sendGroupUpdate(groupMasterKey, decryptedGroup, decryptedChange, signedGroupChange);
|
sendGroupUpdate(groupMasterKey, new GroupMutation(decryptedGroup, decryptedChange, newGroup), signedGroupChange);
|
||||||
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
} catch (VerificationFailedException | InvalidGroupStateException | NotAbleToApplyGroupV2ChangeException e) {
|
||||||
throw new GroupChangeFailedException(e);
|
throw new GroupChangeFailedException(e);
|
||||||
}
|
}
|
||||||
|
@ -959,13 +962,12 @@ final class GroupManagerV2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull RecipientAndThread sendGroupUpdate(@NonNull GroupMasterKey masterKey,
|
private @NonNull RecipientAndThread sendGroupUpdate(@NonNull GroupMasterKey masterKey,
|
||||||
@NonNull DecryptedGroup decryptedGroup,
|
@NonNull GroupMutation groupMutation,
|
||||||
@Nullable DecryptedGroupChange plainGroupChange,
|
|
||||||
@Nullable GroupChange signedGroupChange)
|
@Nullable GroupChange signedGroupChange)
|
||||||
{
|
{
|
||||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange, signedGroupChange);
|
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, groupMutation, signedGroupChange);
|
||||||
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
||||||
decryptedGroupV2Context,
|
decryptedGroupV2Context,
|
||||||
null,
|
null,
|
||||||
|
@ -977,8 +979,11 @@ final class GroupManagerV2 {
|
||||||
Collections.emptyList(),
|
Collections.emptyList(),
|
||||||
Collections.emptyList());
|
Collections.emptyList());
|
||||||
|
|
||||||
|
|
||||||
|
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
||||||
|
|
||||||
if (plainGroupChange != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(plainGroupChange)) {
|
if (plainGroupChange != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(plainGroupChange)) {
|
||||||
ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, decryptedGroup, outgoingMessage));
|
ApplicationDependencies.getJobManager().add(PushGroupSilentUpdateSendJob.create(context, groupId, groupMutation.getNewGroupState(), outgoingMessage));
|
||||||
return new RecipientAndThread(groupRecipient, -1);
|
return new RecipientAndThread(groupRecipient, -1);
|
||||||
} else {
|
} else {
|
||||||
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
long threadId = MessageSender.send(context, outgoingMessage, -1, false, null);
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.thoughtcrime.securesms.groups;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||||
|
|
||||||
|
public final class GroupMutation {
|
||||||
|
@Nullable private final DecryptedGroup previousGroupState;
|
||||||
|
@Nullable private final DecryptedGroupChange groupChange;
|
||||||
|
@NonNull private final DecryptedGroup newGroupState;
|
||||||
|
|
||||||
|
public GroupMutation(@Nullable DecryptedGroup previousGroupState, @Nullable DecryptedGroupChange groupChange, @NonNull DecryptedGroup newGroupState) {
|
||||||
|
this.previousGroupState = previousGroupState;
|
||||||
|
this.groupChange = groupChange;
|
||||||
|
this.newGroupState = newGroupState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable DecryptedGroup getPreviousGroupState() {
|
||||||
|
return previousGroupState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable DecryptedGroupChange getGroupChange() {
|
||||||
|
return groupChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull DecryptedGroup getNewGroupState() {
|
||||||
|
return newGroupState;
|
||||||
|
}
|
||||||
|
}
|
|
@ -49,12 +49,13 @@ public final class GroupProtoUtil {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
|
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
|
||||||
@NonNull DecryptedGroup decryptedGroup,
|
@NonNull GroupMutation groupMutation,
|
||||||
@Nullable DecryptedGroupChange plainGroupChange,
|
|
||||||
@Nullable GroupChange signedServerChange)
|
@Nullable GroupChange signedServerChange)
|
||||||
{
|
{
|
||||||
int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision();
|
DecryptedGroupChange plainGroupChange = groupMutation.getGroupChange();
|
||||||
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
|
DecryptedGroup decryptedGroup = groupMutation.getNewGroupState();
|
||||||
|
int revision = plainGroupChange != null ? plainGroupChange.getRevision() : decryptedGroup.getRevision();
|
||||||
|
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
|
||||||
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
|
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
|
||||||
.setRevision(revision);
|
.setRevision(revision);
|
||||||
|
|
||||||
|
@ -66,6 +67,10 @@ public final class GroupProtoUtil {
|
||||||
.setContext(contextBuilder.build())
|
.setContext(contextBuilder.build())
|
||||||
.setGroupState(decryptedGroup);
|
.setGroupState(decryptedGroup);
|
||||||
|
|
||||||
|
if (groupMutation.getPreviousGroupState() != null) {
|
||||||
|
builder.setPreviousGroupState(groupMutation.getPreviousGroupState());
|
||||||
|
}
|
||||||
|
|
||||||
if (plainGroupChange != null) {
|
if (plainGroupChange != null) {
|
||||||
builder.setChange(plainGroupChange);
|
builder.setChange(plainGroupChange);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
import org.thoughtcrime.securesms.database.model.databaseprotos.DecryptedGroupV2Context;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.GroupId;
|
import org.thoughtcrime.securesms.groups.GroupId;
|
||||||
|
import org.thoughtcrime.securesms.groups.GroupMutation;
|
||||||
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
|
import org.thoughtcrime.securesms.groups.GroupNotAMemberException;
|
||||||
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
|
import org.thoughtcrime.securesms.groups.GroupProtoUtil;
|
||||||
import org.thoughtcrime.securesms.groups.GroupsV2Authorization;
|
import org.thoughtcrime.securesms.groups.GroupsV2Authorization;
|
||||||
|
@ -226,9 +227,9 @@ public final class GroupsV2StateProcessor {
|
||||||
determineProfileSharing(inputGroupState, newLocalState);
|
determineProfileSharing(inputGroupState, newLocalState);
|
||||||
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
if (localState != null && localState.getRevision() == GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION) {
|
||||||
Log.i(TAG, "Inserting single update message for restore placeholder");
|
Log.i(TAG, "Inserting single update message for restore placeholder");
|
||||||
insertUpdateMessages(timestamp, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
|
insertUpdateMessages(timestamp, null, Collections.singleton(new LocalGroupLogEntry(newLocalState, null)));
|
||||||
} else {
|
} else {
|
||||||
insertUpdateMessages(timestamp, advanceGroupStateResult.getProcessedLogEntries());
|
insertUpdateMessages(timestamp, localState, advanceGroupStateResult.getProcessedLogEntries());
|
||||||
}
|
}
|
||||||
persistLearnedProfileKeys(inputGroupState);
|
persistLearnedProfileKeys(inputGroupState);
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ public final class GroupsV2StateProcessor {
|
||||||
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
|
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange, null);
|
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(decryptedGroup, simulatedGroupChange, simulatedGroupState), null);
|
||||||
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
||||||
decryptedGroupV2Context,
|
decryptedGroupV2Context,
|
||||||
null,
|
null,
|
||||||
|
@ -362,13 +363,17 @@ public final class GroupsV2StateProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void insertUpdateMessages(long timestamp, Collection<LocalGroupLogEntry> processedLogEntries) {
|
private void insertUpdateMessages(long timestamp,
|
||||||
|
@Nullable DecryptedGroup previousGroupState,
|
||||||
|
Collection<LocalGroupLogEntry> processedLogEntries)
|
||||||
|
{
|
||||||
for (LocalGroupLogEntry entry : processedLogEntries) {
|
for (LocalGroupLogEntry entry : processedLogEntries) {
|
||||||
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) {
|
if (entry.getChange() != null && DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(entry.getChange()) && !DecryptedGroupUtil.changeIsEmpty(entry.getChange())) {
|
||||||
Log.d(TAG, "Skipping profile key changes only update message");
|
Log.d(TAG, "Skipping profile key changes only update message");
|
||||||
} else {
|
} else {
|
||||||
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange(), null), timestamp);
|
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, new GroupMutation(previousGroupState, entry.getChange(), entry.getGroup()), null), timestamp);
|
||||||
}
|
}
|
||||||
|
previousGroupState = entry.getGroup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,9 +28,10 @@ import "SignalService.proto";
|
||||||
import "DecryptedGroups.proto";
|
import "DecryptedGroups.proto";
|
||||||
|
|
||||||
message DecryptedGroupV2Context {
|
message DecryptedGroupV2Context {
|
||||||
signalservice.GroupContextV2 context = 1;
|
signalservice.GroupContextV2 context = 1;
|
||||||
DecryptedGroupChange change = 2;
|
DecryptedGroupChange change = 2;
|
||||||
DecryptedGroup groupState = 3;
|
DecryptedGroup groupState = 3;
|
||||||
|
DecryptedGroup previousGroupState = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TemporalAuthCredentialResponse {
|
message TemporalAuthCredentialResponse {
|
||||||
|
|
|
@ -1013,6 +1013,12 @@
|
||||||
<string name="MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off">The group link has been turned on with admin approval off.</string>
|
<string name="MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_off">The group link has been turned on with admin approval off.</string>
|
||||||
<string name="MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on">The group link has been turned on with admin approval on.</string>
|
<string name="MessageRecord_the_group_link_has_been_turned_on_with_admin_approval_on">The group link has been turned on with admin approval on.</string>
|
||||||
<string name="MessageRecord_the_group_link_has_been_turned_off">The group link has been turned off.</string>
|
<string name="MessageRecord_the_group_link_has_been_turned_off">The group link has been turned off.</string>
|
||||||
|
<string name="MessageRecord_you_turned_off_admin_approval_for_the_group_link">You turned off admin approval for the group link.</string>
|
||||||
|
<string name="MessageRecord_s_turned_off_admin_approval_for_the_group_link">%1$s turned off admin approval for the group link.</string>
|
||||||
|
<string name="MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_off">The admin approval for the group link has been turned off.</string>
|
||||||
|
<string name="MessageRecord_you_turned_on_admin_approval_for_the_group_link">You turned on admin approval for the group link.</string>
|
||||||
|
<string name="MessageRecord_s_turned_on_admin_approval_for_the_group_link">%1$s turned on admin approval for the group link.</string>
|
||||||
|
<string name="MessageRecord_the_admin_approval_for_the_group_link_has_been_turned_on">The admin approval for the group link has been turned on.</string>
|
||||||
|
|
||||||
<!-- GV2 group link reset -->
|
<!-- GV2 group link reset -->
|
||||||
<string name="MessageRecord_you_reset_the_group_link">You reset the group link.</string>
|
<string name="MessageRecord_you_reset_the_group_link">You reset the group link.</string>
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database.model;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.test.core.app.ApplicationProvider;
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
|
||||||
import com.annimon.stream.Stream;
|
import com.annimon.stream.Stream;
|
||||||
|
@ -35,6 +36,7 @@ import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static org.hamcrest.CoreMatchers.is;
|
import static org.hamcrest.CoreMatchers.is;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -921,6 +923,56 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
assertThat(describeChange(change), is(singletonList("The group link has been turned off.")));
|
assertThat(describeChange(change), is(singletonList("The group link has been turned off.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Group link with known previous group state
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void group_link_access_from_unknown_to_administrator() {
|
||||||
|
assertEquals("You turned on the group link with admin approval on.", describeGroupLinkChange(you, AccessControl.AccessRequired.UNKNOWN, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
assertEquals("Alice turned on the group link with admin approval on.", describeGroupLinkChange(alice, AccessControl.AccessRequired.UNKNOWN, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
assertEquals("The group link has been turned on with admin approval on.", describeGroupLinkChange(null, AccessControl.AccessRequired.UNKNOWN, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void group_link_access_from_administrator_to_unsatisfiable() {
|
||||||
|
assertEquals("You turned off the group link.", describeGroupLinkChange(you, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.UNSATISFIABLE));
|
||||||
|
assertEquals("Bob turned off the group link.", describeGroupLinkChange(bob, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.UNSATISFIABLE));
|
||||||
|
assertEquals("The group link has been turned off.", describeGroupLinkChange(null, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.UNSATISFIABLE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void group_link_access_from_unsatisfiable_to_administrator() {
|
||||||
|
assertEquals("You turned on the group link with admin approval on.", describeGroupLinkChange(you, AccessControl.AccessRequired.UNSATISFIABLE, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
assertEquals("Alice turned on the group link with admin approval on.", describeGroupLinkChange(alice, AccessControl.AccessRequired.UNSATISFIABLE, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
assertEquals("The group link has been turned on with admin approval on.", describeGroupLinkChange(null, AccessControl.AccessRequired.UNSATISFIABLE, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void group_link_access_from_administrator_to_any() {
|
||||||
|
assertEquals("You turned off admin approval for the group link.", describeGroupLinkChange(you, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.ANY));
|
||||||
|
assertEquals("Bob turned off admin approval for the group link.", describeGroupLinkChange(bob, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.ANY));
|
||||||
|
assertEquals("The admin approval for the group link has been turned off.", describeGroupLinkChange(null, AccessControl.AccessRequired.ADMINISTRATOR, AccessControl.AccessRequired.ANY));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void group_link_access_from_any_to_administrator() {
|
||||||
|
assertEquals("You turned on admin approval for the group link.", describeGroupLinkChange(you, AccessControl.AccessRequired.ANY, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
assertEquals("Bob turned on admin approval for the group link.", describeGroupLinkChange(bob, AccessControl.AccessRequired.ANY, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
assertEquals("The admin approval for the group link has been turned on.", describeGroupLinkChange(null, AccessControl.AccessRequired.ANY, AccessControl.AccessRequired.ADMINISTRATOR));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String describeGroupLinkChange(@Nullable UUID editor, @NonNull AccessControl.AccessRequired fromAccess, AccessControl.AccessRequired toAccess){
|
||||||
|
DecryptedGroup previousGroupState = DecryptedGroup.newBuilder()
|
||||||
|
.setAccessControl(AccessControl.newBuilder()
|
||||||
|
.setAddFromInviteLink(fromAccess))
|
||||||
|
.build();
|
||||||
|
DecryptedGroupChange change = (editor != null ? changeBy(editor) : changeByUnknown()).inviteLinkAccess(toAccess)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
List<String> strings = describeChange(previousGroupState, change);
|
||||||
|
assertEquals(1, strings.size());
|
||||||
|
return strings.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
// Group link reset
|
// Group link reset
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -1271,8 +1323,14 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
private @NonNull List<String> describeChange(@NonNull DecryptedGroupChange change) {
|
||||||
|
return describeChange(null, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
private @NonNull List<String> describeChange(@Nullable DecryptedGroup previousGroupState,
|
||||||
|
@NonNull DecryptedGroupChange change)
|
||||||
|
{
|
||||||
MainThreadUtil.setMainThread(false);
|
MainThreadUtil.setMainThread(false);
|
||||||
return Stream.of(producer.describeChanges(change))
|
return Stream.of(producer.describeChanges(previousGroupState, change))
|
||||||
.map(UpdateDescription::getString)
|
.map(UpdateDescription::getString)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
@ -1291,7 +1349,7 @@ public final class GroupsV2UpdateMessageProducerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertSingleChangeMentioning(DecryptedGroupChange change, List<UUID> expectedMentions) {
|
private void assertSingleChangeMentioning(DecryptedGroupChange change, List<UUID> expectedMentions) {
|
||||||
List<UpdateDescription> changes = producer.describeChanges(change);
|
List<UpdateDescription> changes = producer.describeChanges(null, change);
|
||||||
|
|
||||||
assertThat(changes.size(), is(1));
|
assertThat(changes.size(), is(1));
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue