Set profile sharing based on who added you to the group.
This commit is contained in:
parent
a870ef0030
commit
c797b09228
7 changed files with 155 additions and 55 deletions
|
@ -1119,19 +1119,19 @@ public class ThreadDatabase extends Database {
|
|||
Recipient resolved = Recipient.resolved(threadRecipientId);
|
||||
if (resolved.isPushGroup()) {
|
||||
if (resolved.isPushV2Group()) {
|
||||
DecryptedGroup decryptedGroup = DatabaseFactory.getGroupDatabase(context).requireGroup(resolved.requireGroupId().requireV2()).requireV2GroupProperties().getDecryptedGroup();
|
||||
Optional<UUID> inviter = DecryptedGroupUtil.findInviter(decryptedGroup.getPendingMembersList(), Recipient.self().getUuid().get());
|
||||
|
||||
if (inviter.isPresent()) {
|
||||
RecipientId recipientId = RecipientId.from(inviter.get(), null);
|
||||
return Extra.forGroupV2invite(recipientId);
|
||||
} else if (decryptedGroup.getRevision() == 0) {
|
||||
Optional<DecryptedMember> foundingMember = DecryptedGroupUtil.firstMember(decryptedGroup.getMembersList());
|
||||
|
||||
if (foundingMember.isPresent()) {
|
||||
return Extra.forGroupMessageRequest(RecipientId.from(UuidUtil.fromByteString(foundingMember.get().getUuid()), null));
|
||||
MessageRecord.InviteAddState inviteAddState = record.getGv2AddInviteState();
|
||||
if (inviteAddState != null) {
|
||||
RecipientId from = RecipientId.from(inviteAddState.getAddedOrInvitedBy(), null);
|
||||
if (inviteAddState.isInvited()) {
|
||||
Log.i(TAG, "GV2 invite message request from " + from);
|
||||
return Extra.forGroupV2invite(from);
|
||||
} else {
|
||||
Log.i(TAG, "GV2 message request from " + from);
|
||||
return Extra.forGroupMessageRequest(from);
|
||||
}
|
||||
}
|
||||
Log.w(TAG, "Falling back to unknown message request state for GV2 message");
|
||||
return Extra.forMessageRequest();
|
||||
} else {
|
||||
RecipientId recipientId = DatabaseFactory.getMmsSmsDatabase(context).getGroupAddedBy(record.getThreadId());
|
||||
|
||||
|
|
|
@ -53,25 +53,26 @@ final class GroupsV2UpdateMessageProducer {
|
|||
/**
|
||||
* Describes a group that is new to you, use this when there is no available change record.
|
||||
* <p>
|
||||
* Invitation and groups you create are the most common cases where no change is available.
|
||||
* Invitation and revision 0 groups are the most common use cases for this.
|
||||
* <p>
|
||||
* When invited, it's possible there's no change available.
|
||||
* <p>
|
||||
* When the revision of the group is 0, the change is very noisy and only the editor is useful.
|
||||
*/
|
||||
UpdateDescription describeNewGroup(@NonNull DecryptedGroup group) {
|
||||
UpdateDescription describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange decryptedGroupChange) {
|
||||
Optional<DecryptedPendingMember> selfPending = DecryptedGroupUtil.findPendingByUuid(group.getPendingMembersList(), selfUuid);
|
||||
if (selfPending.isPresent()) {
|
||||
return updateDescription(selfPending.get().getAddedByUuid(), inviteBy -> context.getString(R.string.MessageRecord_s_invited_you_to_the_group, inviteBy));
|
||||
}
|
||||
|
||||
if (group.getRevision() == 0) {
|
||||
Optional<DecryptedMember> foundingMember = DecryptedGroupUtil.firstMember(group.getMembersList());
|
||||
if (foundingMember.isPresent()) {
|
||||
ByteString foundingMemberUuid = foundingMember.get().getUuid();
|
||||
ByteString foundingMemberUuid = decryptedGroupChange.getEditor();
|
||||
if (!foundingMemberUuid.isEmpty()) {
|
||||
if (selfUuidBytes.equals(foundingMemberUuid)) {
|
||||
return updateDescription(context.getString(R.string.MessageRecord_you_created_the_group));
|
||||
} else {
|
||||
return updateDescription(foundingMemberUuid, creator -> context.getString(R.string.MessageRecord_s_added_you, creator));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (DecryptedGroupUtil.findMemberByUuid(group.getMembersList(), selfUuid).isPresent()) {
|
||||
return updateDescription(context.getString(R.string.MessageRecord_you_joined_the_group));
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.text.style.StyleSpan;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.SmsDatabase;
|
||||
|
@ -41,6 +42,7 @@ import org.thoughtcrime.securesms.util.ExpirationUtil;
|
|||
import org.thoughtcrime.securesms.util.GroupUtil;
|
||||
import org.thoughtcrime.securesms.util.StringUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Function;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
import org.whispersystems.signalservice.api.util.UuidUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -177,7 +179,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||
if (decryptedGroupV2Context.hasChange() && decryptedGroupV2Context.getGroupState().getRevision() != 0) {
|
||||
return UpdateDescription.concatWithNewLines(updateMessageProducer.describeChanges(decryptedGroupV2Context.getChange()));
|
||||
} else {
|
||||
return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState());
|
||||
return updateMessageProducer.describeNewGroup(decryptedGroupV2Context.getGroupState(), decryptedGroupV2Context.getChange());
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "GV2 Message update detail could not be read", e);
|
||||
|
@ -185,6 +187,29 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||
}
|
||||
}
|
||||
|
||||
public @Nullable InviteAddState getGv2AddInviteState() {
|
||||
try {
|
||||
byte[] decoded = Base64.decode(getBody());
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = DecryptedGroupV2Context.parseFrom(decoded);
|
||||
DecryptedGroup groupState = decryptedGroupV2Context.getGroupState();
|
||||
boolean invited = DecryptedGroupUtil.findPendingByUuid(groupState.getPendingMembersList(), Recipient.self().requireUuid()).isPresent();
|
||||
|
||||
if (decryptedGroupV2Context.hasChange()) {
|
||||
UUID changeEditor = UuidUtil.fromByteStringOrNull(decryptedGroupV2Context.getChange().getEditor());
|
||||
|
||||
if (changeEditor != null) {
|
||||
return new InviteAddState(invited, changeEditor);
|
||||
}
|
||||
}
|
||||
|
||||
Log.w(TAG, "GV2 Message editor could not be determined");
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "GV2 Message update detail could not be read", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static @NonNull UpdateDescription fromRecipient(@NonNull Recipient recipient, @NonNull Function<Recipient, String> stringFunction) {
|
||||
return UpdateDescription.mentioning(Collections.singletonList(recipient.getUuid().or(UuidUtil.UNKNOWN_UUID)), () -> stringFunction.apply(recipient.resolve()));
|
||||
}
|
||||
|
@ -378,4 +403,23 @@ public abstract class MessageRecord extends DisplayRecord {
|
|||
public boolean hasSelfMention() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static final class InviteAddState {
|
||||
|
||||
private final boolean invited;
|
||||
private final UUID addedOrInvitedBy;
|
||||
|
||||
public InviteAddState(boolean invited, @NonNull UUID addedOrInvitedBy) {
|
||||
this.invited = invited;
|
||||
this.addedOrInvitedBy = addedOrInvitedBy;
|
||||
}
|
||||
|
||||
public @NonNull UUID getAddedOrInvitedBy() {
|
||||
return addedOrInvitedBy;
|
||||
}
|
||||
|
||||
public boolean isInvited() {
|
||||
return invited;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.sms.MessageSender;
|
|||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupCandidate;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeReconstruct;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupChangeUtil;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
|
||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Api;
|
||||
|
@ -188,7 +189,11 @@ final class GroupManagerV2 {
|
|||
groupDatabase.onAvatarUpdated(groupId, avatar != null);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
|
||||
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, decryptedGroup, null, null);
|
||||
DecryptedGroupChange groupChange = DecryptedGroupChange.newBuilder(GroupChangeReconstruct.reconstructGroupChange(DecryptedGroup.newBuilder().build(), decryptedGroup))
|
||||
.setEditor(UuidUtil.toByteString(selfUuid))
|
||||
.build();
|
||||
|
||||
RecipientAndThread recipientAndThread = sendGroupUpdate(masterKey, decryptedGroup, groupChange, null);
|
||||
|
||||
return new GroupManager.GroupActionResult(recipientAndThread.groupRecipient,
|
||||
recipientAndThread.threadId,
|
||||
|
|
|
@ -7,6 +7,8 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
|
@ -209,6 +211,7 @@ public final class GroupsV2StateProcessor {
|
|||
}
|
||||
|
||||
updateLocalDatabaseGroupState(inputGroupState, newLocalState);
|
||||
determineProfileSharing(inputGroupState, newLocalState);
|
||||
insertUpdateMessages(timestamp, advanceGroupStateResult.getProcessedLogEntries());
|
||||
persistLearnedProfileKeys(inputGroupState);
|
||||
|
||||
|
@ -293,30 +296,52 @@ public final class GroupsV2StateProcessor {
|
|||
jobManager.add(new AvatarGroupsV2DownloadJob(groupId, newLocalState.getAvatar()));
|
||||
}
|
||||
|
||||
boolean fullMemberPostUpdate = GroupProtoUtil.isMember(Recipient.self().getUuid().get(), newLocalState.getMembersList());
|
||||
boolean trustedAdder = false;
|
||||
|
||||
if (newLocalState.getRevision() == 0) {
|
||||
Optional<DecryptedMember> foundingMember = DecryptedGroupUtil.firstMember(newLocalState.getMembersList());
|
||||
|
||||
if (foundingMember.isPresent()) {
|
||||
UUID foundingMemberUuid = UuidUtil.fromByteString(foundingMember.get().getUuid());
|
||||
Recipient foundingRecipient = Recipient.externalPush(context, foundingMemberUuid, null, false);
|
||||
|
||||
if (foundingRecipient.isSystemContact() || foundingRecipient.isProfileSharing()) {
|
||||
Log.i(TAG, "Group 'adder' is trusted. contact: " + foundingRecipient.isSystemContact() + ", profileSharing: " + foundingRecipient.isProfileSharing());
|
||||
trustedAdder = true;
|
||||
determineProfileSharing(inputGroupState, newLocalState);
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Could not find founding member during gv2 create. Not enabling profile sharing.");
|
||||
|
||||
private void determineProfileSharing(@NonNull GlobalGroupState inputGroupState,
|
||||
@NonNull DecryptedGroup newLocalState)
|
||||
{
|
||||
if (inputGroupState.getLocalState() != null) {
|
||||
boolean wasAMemberAlready = DecryptedGroupUtil.findMemberByUuid(inputGroupState.getLocalState().getMembersList(), Recipient.self().getUuid().get()).isPresent();
|
||||
|
||||
if (wasAMemberAlready) {
|
||||
Log.i(TAG, "Skipping profile sharing detection as was already a full member before update");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (fullMemberPostUpdate && trustedAdder) {
|
||||
Optional<DecryptedMember> selfAsMemberOptional = DecryptedGroupUtil.findMemberByUuid(newLocalState.getMembersList(), Recipient.self().getUuid().get());
|
||||
|
||||
if (selfAsMemberOptional.isPresent()) {
|
||||
DecryptedMember selfAsMember = selfAsMemberOptional.get();
|
||||
int revisionJoinedAt = selfAsMember.getJoinedAtRevision();
|
||||
|
||||
Optional<Recipient> addedByOptional = Stream.of(inputGroupState.getServerHistory())
|
||||
.map(ServerGroupLogEntry::getChange)
|
||||
.filter(c -> c != null && c.getRevision() == revisionJoinedAt)
|
||||
.findFirst()
|
||||
.map(c -> Optional.fromNullable(UuidUtil.fromByteStringOrNull(c.getEditor()))
|
||||
.transform(a -> Recipient.externalPush(context, UuidUtil.fromByteStringOrNull(c.getEditor()), null, false)))
|
||||
.orElse(Optional.absent());
|
||||
|
||||
if (addedByOptional.isPresent()) {
|
||||
Recipient addedBy = addedByOptional.get();
|
||||
|
||||
Log.i(TAG, String.format("Added as a full member of %s by %s", groupId, addedBy.getId()));
|
||||
|
||||
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.externalGroup(context, groupId).getId(), true);
|
||||
} else {
|
||||
Log.i(TAG, "Added to a group, but not enabling profile sharing. fullMember: " + fullMemberPostUpdate + ", trustedAdded: " + trustedAdder);
|
||||
Log.i(TAG, "Added to a group, but not enabling profile sharing, as 'adder' is not trusted");
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Could not find founding member during gv2 create. Not enabling profile sharing.");
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, String.format("Added to %s, but not enabling profile sharing as not a fullMember.", groupId));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1196,12 +1196,36 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
|
||||
// Group state without a change record
|
||||
|
||||
@Test
|
||||
public void you_created_a_group_change_not_found() {
|
||||
DecryptedGroup group = newGroupBy(you, 0)
|
||||
.build();
|
||||
|
||||
assertThat(describeNewGroup(group), is("You joined the group."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void you_created_a_group() {
|
||||
DecryptedGroup group = newGroupBy(you, 0)
|
||||
.build();
|
||||
|
||||
assertThat(describeNewGroup(group), is("You created the group."));
|
||||
DecryptedGroupChange change = changeBy(you)
|
||||
.addMember(alice)
|
||||
.addMember(you)
|
||||
.addMember(bob)
|
||||
.title("New title")
|
||||
.build();
|
||||
|
||||
assertThat(describeNewGroup(group, change), is("You created the group."));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void alice_created_a_group_change_not_found() {
|
||||
DecryptedGroup group = newGroupBy(alice, 0)
|
||||
.member(you)
|
||||
.build();
|
||||
|
||||
assertThat(describeNewGroup(group), is("You joined the group."));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1210,7 +1234,14 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
.member(you)
|
||||
.build();
|
||||
|
||||
assertThat(describeNewGroup(group), is("Alice added you to the group."));
|
||||
DecryptedGroupChange change = changeBy(alice)
|
||||
.addMember(you)
|
||||
.addMember(alice)
|
||||
.addMember(bob)
|
||||
.title("New title")
|
||||
.build();
|
||||
|
||||
assertThat(describeNewGroup(group, change), is("Alice added you to the group."));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1247,8 +1278,12 @@ public final class GroupsV2UpdateMessageProducerTest {
|
|||
}
|
||||
|
||||
private @NonNull String describeNewGroup(@NonNull DecryptedGroup group) {
|
||||
return describeNewGroup(group, DecryptedGroupChange.getDefaultInstance());
|
||||
}
|
||||
|
||||
private @NonNull String describeNewGroup(@NonNull DecryptedGroup group, @NonNull DecryptedGroupChange groupChange) {
|
||||
MainThreadUtil.setMainThread(false);
|
||||
return producer.describeNewGroup(group).getString();
|
||||
return producer.describeNewGroup(group, groupChange).getString();
|
||||
}
|
||||
|
||||
private static GroupStateBuilder newGroupBy(UUID foundingMember, int revision) {
|
||||
|
|
|
@ -160,16 +160,6 @@ public final class DecryptedGroupUtil {
|
|||
return Optional.absent();
|
||||
}
|
||||
|
||||
public static Optional<DecryptedMember> firstMember(Collection<DecryptedMember> members) {
|
||||
Iterator<DecryptedMember> iterator = members.iterator();
|
||||
|
||||
if (iterator.hasNext()) {
|
||||
return Optional.of(iterator.next());
|
||||
} else {
|
||||
return Optional.absent();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<DecryptedPendingMember> findPendingByUuid(Collection<DecryptedPendingMember> members, UUID uuid) {
|
||||
ByteString uuidBytes = UuidUtil.toByteString(uuid);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue