GV2 Group Manager.

This commit is contained in:
Alan Evans
2020-05-01 19:13:23 -03:00
committed by Alex Hart
parent ff28d72db6
commit 48a693793f
42 changed files with 1877 additions and 288 deletions

View File

@@ -267,13 +267,14 @@ public final class GroupDatabase extends Database {
create(groupId, null, members, null, null, null, null);
}
public void create(@NonNull GroupId.V2 groupId,
@Nullable SignalServiceAttachmentPointer avatar,
@Nullable String relay,
@NonNull GroupMasterKey groupMasterKey,
@NonNull DecryptedGroup groupState)
public GroupId.V2 create(@NonNull GroupMasterKey groupMasterKey,
@NonNull DecryptedGroup groupState)
{
create(groupId, groupState.getTitle(), Collections.emptyList(), avatar, relay, groupMasterKey, groupState);
GroupId.V2 groupId = GroupId.v2(groupMasterKey);
create(groupId, groupState.getTitle(), Collections.emptyList(), null, null, groupMasterKey, groupState);
return groupId;
}
/**
@@ -287,12 +288,14 @@ public final class GroupDatabase extends Database {
@Nullable GroupMasterKey groupMasterKey,
@Nullable DecryptedGroup groupState)
{
List<RecipientId> members = new ArrayList<>(new HashSet<>(memberCollection));
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
RecipientId groupRecipientId = recipientDatabase.getOrInsertFromGroupId(groupId);
List<RecipientId> members = new ArrayList<>(new HashSet<>(memberCollection));
Collections.sort(members);
ContentValues contentValues = new ContentValues();
contentValues.put(RECIPIENT_ID, DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId).serialize());
contentValues.put(RECIPIENT_ID, groupRecipientId.serialize());
contentValues.put(GROUP_ID, groupId.toString());
contentValues.put(TITLE, title);
contentValues.put(MEMBERS, RecipientId.toSerializedList(members));
@@ -328,8 +331,11 @@ public final class GroupDatabase extends Database {
databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues);
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
if (groupState != null && groupState.hasDisappearingMessagesTimer()) {
recipientDatabase.setExpireMessages(groupRecipientId, groupState.getDisappearingMessagesTimer().getDuration());
}
Recipient.live(groupRecipientId).refresh();
notifyConversationListListeners();
}
@@ -365,8 +371,10 @@ public final class GroupDatabase extends Database {
}
public void update(@NonNull GroupId.V2 groupId, @NonNull DecryptedGroup decryptedGroup) {
String title = decryptedGroup.getTitle();
ContentValues contentValues = new ContentValues();
RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
RecipientId groupRecipientId = recipientDatabase.getOrInsertFromGroupId(groupId);
String title = decryptedGroup.getTitle();
ContentValues contentValues = new ContentValues();
contentValues.put(TITLE, title);
contentValues.put(V2_REVISION, decryptedGroup.getVersion());
@@ -377,8 +385,11 @@ public final class GroupDatabase extends Database {
GROUP_ID + " = ?",
new String[]{ groupId.toString() });
RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId);
Recipient.live(groupRecipient).refresh();
if (decryptedGroup.hasDisappearingMessagesTimer()) {
recipientDatabase.setExpireMessages(groupRecipientId, decryptedGroup.getDisappearingMessagesTimer().getDuration());
}
Recipient.live(groupRecipientId).refresh();
notifyConversationListListeners();
}
@@ -499,6 +510,21 @@ public final class GroupDatabase extends Database {
return RecipientId.toSerializedList(groupMembers);
}
public List<GroupId.V2> getAllGroupV2Ids() {
List<GroupId.V2> result = new LinkedList<>();
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{ GROUP_ID }, null, null, null, null, null)) {
while (cursor.moveToNext()) {
GroupId groupId = GroupId.parseOrThrow(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)));
if (groupId.isV2()) {
result.add(groupId.requireV2());
}
}
}
return result;
}
public static class Reader implements Closeable {
private final Cursor cursor;
@@ -750,8 +776,8 @@ public final class GroupDatabase extends Database {
FULL_MEMBERS_AND_PENDING_INCLUDING_SELF(true, true),
FULL_MEMBERS_AND_PENDING_EXCLUDING_SELF(false, true);
private boolean includeSelf;
private boolean includePending;
private final boolean includeSelf;
private final boolean includePending;
MemberSet(boolean includeSelf, boolean includePending) {
this.includeSelf = includeSelf;

View File

@@ -17,10 +17,12 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.signal.zkgroup.profiles.ProfileKey;
import org.signal.zkgroup.profiles.ProfileKeyCredential;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.crypto.ProfileKeyUtil;
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.profiles.ProfileName;
@@ -47,11 +49,14 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@@ -977,6 +982,30 @@ public class RecipientDatabase extends Database {
Recipient.live(id).refresh();
StorageSyncHelper.scheduleSyncForDataChange();
return true;
}
return false;
}
/**
* Sets the profile key iff currently null.
* <p>
* If it sets it, it also clears out the profile key credential and resets the unidentified access mode.
* @return true iff changed.
*/
public boolean setProfileKeyIfAbsent(@NonNull RecipientId id, @NonNull ProfileKey profileKey) {
SQLiteDatabase database = databaseHelper.getWritableDatabase();
String selection = ID + " = ? AND " + PROFILE_KEY + " is NULL";
String[] args = new String[]{id.serialize()};
ContentValues valuesToSet = new ContentValues(3);
valuesToSet.put(PROFILE_KEY, Base64.encodeBytes(profileKey.serialize()));
valuesToSet.putNull(PROFILE_KEY_CREDENTIAL);
valuesToSet.put(UNIDENTIFIED_ACCESS_MODE, UnidentifiedAccessMode.UNKNOWN.getMode());
if (database.update(TABLE_NAME, valuesToSet, selection, args) > 0) {
markDirty(id, DirtyState.UPDATE);
Recipient.live(id).refresh();
return true;
} else {
return false;
}
@@ -1013,6 +1042,56 @@ public class RecipientDatabase extends Database {
}
}
/**
* Fills in gaps (nulls) in profile key knowledge from new profile keys.
* <p>
* If from authoritative source, this will overwrite local, otherwise it will only write to the
* database if missing.
*/
public Collection<RecipientId> persistProfileKeySet(@NonNull ProfileKeySet profileKeySet) {
Map<UUID, ProfileKey> profileKeys = profileKeySet.getProfileKeys();
Map<UUID, ProfileKey> authoritativeProfileKeys = profileKeySet.getAuthoritativeProfileKeys();
int totalKeys = profileKeys.size() + authoritativeProfileKeys.size();
if (totalKeys == 0) {
return Collections.emptyList();
}
Log.i(TAG, String.format(Locale.US, "Persisting %d Profile keys, %d of which are authoritative", totalKeys, authoritativeProfileKeys.size()));
HashSet<RecipientId> updated = new HashSet<>(totalKeys);
RecipientId selfId = Recipient.self().getId();
for (Map.Entry<UUID, ProfileKey> entry : profileKeys.entrySet()) {
RecipientId recipientId = getOrInsertFromUuid(entry.getKey());
if (setProfileKeyIfAbsent(recipientId, entry.getValue())) {
Log.i(TAG, "Learned new profile key");
updated.add(recipientId);
}
}
for (Map.Entry<UUID, ProfileKey> entry : authoritativeProfileKeys.entrySet()) {
RecipientId recipientId = getOrInsertFromUuid(entry.getKey());
if (selfId.equals(recipientId)) {
Log.i(TAG, "Seen authoritative update for self");
if (!entry.getValue().equals(ProfileKeyUtil.getSelfProfileKey())) {
Log.w(TAG, "Seen authoritative update for self that didn't match local, scheduling storage sync");
StorageSyncHelper.scheduleSyncForDataChange();
}
} else {
Log.i(TAG, String.format("Profile key from owner %s", recipientId));
if (setProfileKey(recipientId, entry.getValue())) {
Log.i(TAG, "Learned new profile key from owner");
updated.add(recipientId);
}
}
}
return updated;
}
public void setProfileName(@NonNull RecipientId id, @NonNull ProfileName profileName) {
ContentValues contentValues = new ContentValues(1);
contentValues.put(PROFILE_GIVEN_NAME, profileName.getGivenName());

View File

@@ -210,23 +210,24 @@ final class GroupsV2UpdateMessageProducer {
private void describePromotePending(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);
for (ByteString member : change.getPromotePendingMembersList()) {
boolean newMemberIsYou = member.equals(youUuid);
for (DecryptedMember newMember : change.getPromotePendingMembersList()) {
ByteString uuid = newMember.getUuid();
boolean newMemberIsYou = uuid.equals(youUuid);
if (editorIsYou) {
if (newMemberIsYou) {
updates.add(context.getString(R.string.MessageRecord_you_accepted_invite));
} else {
updates.add(context.getString(R.string.MessageRecord_you_added_invited_member_s, describe(member)));
updates.add(context.getString(R.string.MessageRecord_you_added_invited_member_s, describe(uuid)));
}
} else {
if (newMemberIsYou) {
updates.add(context.getString(R.string.MessageRecord_s_added_you, describe(change.getEditor())));
} else {
if (member.equals(change.getEditor())) {
updates.add(context.getString(R.string.MessageRecord_s_accepted_invite, describe(member)));
if (uuid.equals(change.getEditor())) {
updates.add(context.getString(R.string.MessageRecord_s_accepted_invite, describe(uuid)));
} else {
updates.add(context.getString(R.string.MessageRecord_s_added_invited_member_s, describe(change.getEditor()), describe(member)));
updates.add(context.getString(R.string.MessageRecord_s_added_invited_member_s, describe(change.getEditor()), describe(uuid)));
}
}
}
@@ -282,7 +283,7 @@ final class GroupsV2UpdateMessageProducer {
}
}
}
private void describeNewMembershipAccess(@NonNull DecryptedGroupChange change, @NonNull List<String> updates) {
boolean editorIsYou = change.getEditor().equals(youUuid);