mirror of
https://github.com/oxen-io/session-android.git
synced 2025-06-08 17:08:34 +00:00
Server signed group v2 changes sent and received P2P.
This commit is contained in:
parent
ec8d5defd4
commit
2f9320989a
@ -148,11 +148,12 @@ public final class GroupManager {
|
||||
public static void updateGroupFromServer(@NonNull Context context,
|
||||
@NonNull GroupMasterKey groupMasterKey,
|
||||
int version,
|
||||
long timestamp)
|
||||
long timestamp,
|
||||
@Nullable byte[] signedGroupChange)
|
||||
throws GroupChangeBusyException, IOException, GroupNotAMemberException
|
||||
{
|
||||
try (GroupManagerV2.GroupUpdater updater = new GroupManagerV2(context).updater(groupMasterKey)) {
|
||||
updater.updateLocalToServerVersion(version, timestamp);
|
||||
updater.updateLocalToServerVersion(version, timestamp, signedGroupChange);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,8 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import org.signal.storageservice.protos.groups.AccessControl;
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.Member;
|
||||
@ -144,7 +146,7 @@ final class GroupManagerV2 {
|
||||
groupDatabase.onAvatarUpdated(groupId, avatar != null);
|
||||
DatabaseFactory.getRecipientDatabase(context).setProfileSharing(groupRecipient.getId(), true);
|
||||
|
||||
return sendGroupUpdate(masterKey, decryptedGroup, null);
|
||||
return sendGroupUpdate(masterKey, decryptedGroup, null, null);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
@ -354,7 +356,7 @@ final class GroupManagerV2 {
|
||||
throws IOException, GroupNotAMemberException, GroupChangeFailedException
|
||||
{
|
||||
GroupsV2StateProcessor.GroupUpdateResult groupUpdateResult = groupsV2StateProcessor.forGroup(groupMasterKey)
|
||||
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis());
|
||||
.updateLocalGroupToRevision(GroupsV2StateProcessor.LATEST, System.currentTimeMillis(), null);
|
||||
|
||||
if (groupUpdateResult.getGroupState() != GroupsV2StateProcessor.GroupState.GROUP_UPDATED || groupUpdateResult.getLatestServer() == null) {
|
||||
throw new GroupChangeFailedException();
|
||||
@ -390,24 +392,24 @@ final class GroupManagerV2 {
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
commitToServer(changeActions);
|
||||
GroupChange signedGroupChange = commitToServer(changeActions);
|
||||
groupDatabase.update(groupId, decryptedGroupState);
|
||||
|
||||
return sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange);
|
||||
return sendGroupUpdate(groupMasterKey, decryptedGroupState, decryptedChange, signedGroupChange);
|
||||
}
|
||||
|
||||
private void commitToServer(GroupChange.Actions change)
|
||||
private GroupChange commitToServer(GroupChange.Actions change)
|
||||
throws GroupNotAMemberException, GroupChangeFailedException, IOException, GroupInsufficientRightsException
|
||||
{
|
||||
try {
|
||||
groupsV2Api.patchGroup(change, groupSecretParams, authorization.getAuthorizationForToday(selfUuid, groupSecretParams));
|
||||
return groupsV2Api.patchGroup(change, authorization.getAuthorizationForToday(selfUuid, groupSecretParams));
|
||||
} catch (NotInGroupException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new GroupNotAMemberException(e);
|
||||
} catch (AuthorizationFailedException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new GroupInsufficientRightsException(e);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException e) {
|
||||
} catch (VerificationFailedException e) {
|
||||
Log.w(TAG, e);
|
||||
throw new GroupChangeFailedException(e);
|
||||
}
|
||||
@ -430,11 +432,25 @@ final class GroupManagerV2 {
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
void updateLocalToServerVersion(int version, long timestamp)
|
||||
void updateLocalToServerVersion(int version, long timestamp, @Nullable byte[] signedGroupChange)
|
||||
throws IOException, GroupNotAMemberException
|
||||
{
|
||||
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
|
||||
.updateLocalGroupToRevision(version, timestamp);
|
||||
new GroupsV2StateProcessor(context).forGroup(groupMasterKey)
|
||||
.updateLocalGroupToRevision(version, timestamp, getDecryptedGroupChange(signedGroupChange));
|
||||
}
|
||||
|
||||
private DecryptedGroupChange getDecryptedGroupChange(@Nullable byte[] signedGroupChange) {
|
||||
if (signedGroupChange != null) {
|
||||
GroupsV2Operations.GroupOperations groupOperations = groupsV2Operations.forGroup(GroupSecretParams.deriveFromMasterKey(groupMasterKey));
|
||||
|
||||
try {
|
||||
return groupOperations.decryptChange(GroupChange.parseFrom(signedGroupChange), true);
|
||||
} catch (VerificationFailedException | InvalidGroupStateException | InvalidProtocolBufferException e) {
|
||||
Log.w(TAG, "Unable to verify supplied group change", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -445,11 +461,12 @@ final class GroupManagerV2 {
|
||||
|
||||
private @NonNull GroupManager.GroupActionResult sendGroupUpdate(@NonNull GroupMasterKey masterKey,
|
||||
@NonNull DecryptedGroup decryptedGroup,
|
||||
@Nullable DecryptedGroupChange plainGroupChange)
|
||||
@Nullable DecryptedGroupChange plainGroupChange,
|
||||
@Nullable GroupChange signedGroupChange)
|
||||
{
|
||||
GroupId.V2 groupId = GroupId.v2(masterKey);
|
||||
Recipient groupRecipient = Recipient.externalGroup(context, groupId);
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange);
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, decryptedGroup, plainGroupChange, signedGroupChange);
|
||||
OutgoingGroupUpdateMessage outgoingMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
||||
decryptedGroupV2Context,
|
||||
null,
|
||||
|
@ -8,6 +8,7 @@ import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.google.protobuf.ByteString;
|
||||
|
||||
import org.signal.storageservice.protos.groups.GroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedMember;
|
||||
@ -48,16 +49,20 @@ public final class GroupProtoUtil {
|
||||
|
||||
public static DecryptedGroupV2Context createDecryptedGroupV2Context(@NonNull GroupMasterKey masterKey,
|
||||
@NonNull DecryptedGroup decryptedGroup,
|
||||
@Nullable DecryptedGroupChange plainGroupChange)
|
||||
@Nullable DecryptedGroupChange plainGroupChange,
|
||||
@Nullable GroupChange signedServerChange)
|
||||
{
|
||||
int version = plainGroupChange != null ? plainGroupChange.getVersion() : decryptedGroup.getVersion();
|
||||
SignalServiceProtos.GroupContextV2 groupContext = SignalServiceProtos.GroupContextV2.newBuilder()
|
||||
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
|
||||
.setRevision(version)
|
||||
.build();
|
||||
SignalServiceProtos.GroupContextV2.Builder contextBuilder = SignalServiceProtos.GroupContextV2.newBuilder()
|
||||
.setMasterKey(ByteString.copyFrom(masterKey.serialize()))
|
||||
.setRevision(version);
|
||||
|
||||
if (signedServerChange != null) {
|
||||
contextBuilder.setGroupChange(signedServerChange.toByteString());
|
||||
}
|
||||
|
||||
DecryptedGroupV2Context.Builder builder = DecryptedGroupV2Context.newBuilder()
|
||||
.setContext(groupContext)
|
||||
.setContext(contextBuilder.build())
|
||||
.setGroupState(decryptedGroup);
|
||||
|
||||
if (plainGroupChange != null) {
|
||||
|
@ -132,21 +132,47 @@ public final class GroupsV2StateProcessor {
|
||||
*/
|
||||
@WorkerThread
|
||||
public GroupUpdateResult updateLocalGroupToRevision(final int revision,
|
||||
final long timestamp)
|
||||
final long timestamp,
|
||||
@Nullable DecryptedGroupChange signedGroupChange)
|
||||
throws IOException, GroupNotAMemberException
|
||||
{
|
||||
if (localIsAtLeast(revision)) {
|
||||
return new GroupUpdateResult(GroupState.GROUP_CONSISTENT_OR_AHEAD, null);
|
||||
}
|
||||
|
||||
GlobalGroupState inputGroupState;
|
||||
try {
|
||||
inputGroupState = queryServer();
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
|
||||
insertGroupLeave();
|
||||
throw e;
|
||||
GlobalGroupState inputGroupState = null;
|
||||
|
||||
DecryptedGroup localState = groupDatabase.getGroup(groupId)
|
||||
.transform(g -> g.requireV2GroupProperties().getDecryptedGroup())
|
||||
.orNull();
|
||||
|
||||
if (signedGroupChange != null &&
|
||||
localState != null &&
|
||||
localState.getVersion() + 1 == signedGroupChange.getVersion() &&
|
||||
revision == signedGroupChange.getVersion())
|
||||
{
|
||||
try {
|
||||
Log.i(TAG, "Applying P2P group change");
|
||||
DecryptedGroup newState = DecryptedGroupUtil.apply(localState, signedGroupChange);
|
||||
|
||||
inputGroupState = new GlobalGroupState(localState, Collections.singletonList(new GroupLogEntry(newState, signedGroupChange)));
|
||||
} catch (DecryptedGroupUtil.NotAbleToApplyChangeException e) {
|
||||
Log.w(TAG, "Unable to apply P2P group change", e);
|
||||
}
|
||||
}
|
||||
|
||||
if (inputGroupState == null) {
|
||||
try {
|
||||
inputGroupState = queryServer(localState);
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Unable to query server for group " + groupId + " server says we're not in group, inserting leave message");
|
||||
insertGroupLeave();
|
||||
throw e;
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Saved server query for group change");
|
||||
}
|
||||
|
||||
AdvanceGroupStateResult advanceGroupStateResult = GroupStateMapper.partiallyAdvanceGroupState(inputGroupState, revision);
|
||||
DecryptedGroup newLocalState = advanceGroupStateResult.getNewGlobalGroupState().getLocalState();
|
||||
|
||||
@ -185,7 +211,7 @@ public final class GroupsV2StateProcessor {
|
||||
.addDeleteMembers(UuidUtil.toByteString(selfUuid))
|
||||
.build();
|
||||
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange);
|
||||
DecryptedGroupV2Context decryptedGroupV2Context = GroupProtoUtil.createDecryptedGroupV2Context(masterKey, simulatedGroupState, simulatedGroupChange, null);
|
||||
OutgoingGroupUpdateMessage leaveMessage = new OutgoingGroupUpdateMessage(groupRecipient,
|
||||
decryptedGroupV2Context,
|
||||
null,
|
||||
@ -245,7 +271,7 @@ public final class GroupsV2StateProcessor {
|
||||
|
||||
private void insertUpdateMessages(long timestamp, Collection<GroupLogEntry> processedLogEntries) {
|
||||
for (GroupLogEntry entry : processedLogEntries) {
|
||||
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange()), timestamp);
|
||||
storeMessage(GroupProtoUtil.createDecryptedGroupV2Context(masterKey, entry.getGroup(), entry.getChange(), null), timestamp);
|
||||
}
|
||||
}
|
||||
|
||||
@ -266,15 +292,12 @@ public final class GroupsV2StateProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
private GlobalGroupState queryServer()
|
||||
private @NonNull GlobalGroupState queryServer(@Nullable DecryptedGroup localState)
|
||||
throws IOException, GroupNotAMemberException
|
||||
{
|
||||
DecryptedGroup latestServerGroup;
|
||||
List<GroupLogEntry> history;
|
||||
UUID selfUuid = Recipient.self().getUuid().get();
|
||||
DecryptedGroup localState = groupDatabase.getGroup(groupId)
|
||||
.transform(g -> g.requireV2GroupProperties().getDecryptedGroup())
|
||||
.orNull();
|
||||
UUID selfUuid = Recipient.self().getUuid().get();
|
||||
|
||||
try {
|
||||
latestServerGroup = groupsV2Api.getGroup(groupSecretParams, groupsV2Authorization.getAuthorizationForToday(selfUuid, groupSecretParams));
|
||||
|
@ -445,7 +445,7 @@ public final class PushProcessMessageJob extends BaseJob {
|
||||
throws IOException, GroupChangeBusyException
|
||||
{
|
||||
try {
|
||||
GroupManager.updateGroupFromServer(context, groupMasterKey, groupV2.getRevision(), content.getTimestamp());
|
||||
GroupManager.updateGroupFromServer(context, groupMasterKey, groupV2.getRevision(), content.getTimestamp(), groupV2.getSignedGroupChange());
|
||||
return true;
|
||||
} catch (GroupNotAMemberException e) {
|
||||
Log.w(TAG, "Ignoring message for a group we're not in");
|
||||
|
@ -87,7 +87,7 @@ public final class RequestGroupV2InfoJob extends BaseJob {
|
||||
return;
|
||||
}
|
||||
|
||||
GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis());
|
||||
GroupManager.updateGroupFromServer(context, group.get().requireV2GroupProperties().getGroupMasterKey(), toRevision, System.currentTimeMillis(), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1072,10 +1072,17 @@ public class SignalServiceMessageSender {
|
||||
}
|
||||
|
||||
private static GroupContextV2 createGroupContent(SignalServiceGroupV2 group) {
|
||||
return GroupContextV2.newBuilder()
|
||||
.setMasterKey(ByteString.copyFrom(group.getMasterKey().serialize()))
|
||||
.setRevision(group.getRevision())
|
||||
.build();
|
||||
GroupContextV2.Builder builder = GroupContextV2.newBuilder()
|
||||
.setMasterKey(ByteString.copyFrom(group.getMasterKey().serialize()))
|
||||
.setRevision(group.getRevision());
|
||||
|
||||
|
||||
byte[] signedGroupChange = group.getSignedGroupChange();
|
||||
if (signedGroupChange != null && signedGroupChange.length <= 2048) {
|
||||
builder.setGroupChange(ByteString.copyFrom(signedGroupChange));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private List<DataMessage.Contact> createSharedContactContent(List<SharedContact> contacts) throws IOException {
|
||||
|
@ -132,15 +132,11 @@ public final class GroupsV2Api {
|
||||
return form.getKey();
|
||||
}
|
||||
|
||||
public DecryptedGroupChange patchGroup(GroupChange.Actions groupChange,
|
||||
GroupSecretParams groupSecretParams,
|
||||
GroupsV2AuthorizationString authorization)
|
||||
throws IOException, VerificationFailedException, InvalidGroupStateException
|
||||
public GroupChange patchGroup(GroupChange.Actions groupChange,
|
||||
GroupsV2AuthorizationString authorization)
|
||||
throws IOException
|
||||
{
|
||||
GroupChange groupChanges = socket.patchGroupsV2Group(groupChange, authorization.toString());
|
||||
|
||||
return groupsOperations.forGroup(groupSecretParams)
|
||||
.decryptChange(groupChanges, true);
|
||||
return socket.patchGroupsV2Group(groupChange, authorization.toString());
|
||||
}
|
||||
|
||||
private static HashMap<Integer, AuthCredentialResponse> parseCredentialResponse(CredentialResponse credentialResponse)
|
||||
|
Loading…
x
Reference in New Issue
Block a user