Prevent rejected/kicked group members from joining again via group link.

fork-5.53.8
Cody Henthorne 2022-03-11 10:08:32 -05:00
rodzic 3503c60fd1
commit eed45b57a1
29 zmienionych plików z 687 dodań i 149 usunięć

Wyświetl plik

@ -12,9 +12,11 @@ import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
import org.signal.zkgroup.VerificationFailedException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.signal.zkgroup.groups.UuidCiphertext;
import org.thoughtcrime.securesms.database.GroupDatabase;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
import org.thoughtcrime.securesms.groups.v2.GroupLinkPassword;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
@ -22,9 +24,11 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.whispersystems.signalservice.api.groupsv2.GroupLinkNotActiveException;
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -157,11 +161,11 @@ public final class GroupManager {
}
@WorkerThread
public static void ejectFromGroup(@NonNull Context context, @NonNull GroupId.V2 groupId, @NonNull Recipient recipient)
public static void ejectAndBanFromGroup(@NonNull Context context, @NonNull GroupId.V2 groupId, @NonNull Recipient recipient)
throws GroupChangeBusyException, GroupChangeFailedException, GroupInsufficientRightsException, GroupNotAMemberException, IOException
{
try (GroupManagerV2.GroupEditor edit = new GroupManagerV2(context).edit(groupId.requireV2())) {
edit.ejectMember(recipient.requireServiceId(), false);
edit.ejectMember(recipient.requireServiceId(), false, true);
Log.i(TAG, "Member removed from group " + groupId);
}
}
@ -273,6 +277,28 @@ public final class GroupManager {
}
}
@WorkerThread
public static void ban(@NonNull Context context,
@NonNull GroupId.V2 groupId,
@NonNull RecipientId recipientId)
throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException
{
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
editor.ban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().uuid()));
}
}
@WorkerThread
public static void unban(@NonNull Context context,
@NonNull GroupId.V2 groupId,
@NonNull RecipientId recipientId)
throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException
{
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
editor.unban(Collections.singleton(Recipient.resolved(recipientId).requireServiceId().uuid()));
}
}
@WorkerThread
public static void applyMembershipAdditionRightsChange(@NonNull Context context,
@NonNull GroupId.V2 groupId,

Wyświetl plik

@ -428,7 +428,7 @@ final class GroupManagerV2 {
.map(r -> Recipient.resolved(r).requireServiceId().uuid())
.collect(Collectors.toSet());
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids));
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true));
}
@WorkerThread
@ -455,15 +455,15 @@ final class GroupManagerV2 {
throw new AssertionError(e);
}
} else {
return ejectMember(ServiceId.from(selfAci.uuid()), true);
return ejectMember(ServiceId.from(selfAci.uuid()), true, false);
}
}
@WorkerThread
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked)
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked, boolean ban)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid())), allowWhenBlocked);
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()), ban), allowWhenBlocked);
}
@WorkerThread
@ -530,6 +530,14 @@ final class GroupManagerV2 {
return commitChangeWithConflictResolution(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get()));
}
public GroupManager.GroupActionResult ban(Set<UUID> uuids) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException {
return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(uuids));
}
public GroupManager.GroupActionResult unban(Set<UUID> uuids) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException {
return commitChangeWithConflictResolution(groupOperations.createUnbanUuidsChange(uuids));
}
@WorkerThread
public GroupManager.GroupActionResult cycleGroupLinkPassword()
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
@ -1094,7 +1102,7 @@ final class GroupManagerV2 {
GroupChange signedGroupChange;
try {
signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids));
signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, false));
} catch (GroupLinkNotActiveException e) {
Log.d(TAG, "Unexpected unable to leave group due to group link off");
throw new GroupChangeFailedException(e);

Wyświetl plik

@ -5,6 +5,8 @@ import android.content.Context;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.recipients.Recipient;
@ -13,50 +15,37 @@ final class RequestConfirmationDialog {
private RequestConfirmationDialog() {
}
/**
* Confirms that you want to approve or deny a request to join the group depending on
* {@param approve}.
*/
static AlertDialog show(@NonNull Context context,
@NonNull Recipient requester,
boolean approve,
@NonNull Runnable onApproveOrDeny)
{
if (approve) {
return showRequestApproveConfirmationDialog(context, requester, onApproveOrDeny);
} else {
return showRequestDenyConfirmationDialog(context, requester, onApproveOrDeny);
}
}
/**
* Confirms that you want to approve a request to join the group.
*/
private static AlertDialog showRequestApproveConfirmationDialog(@NonNull Context context,
@NonNull Recipient requester,
@NonNull Runnable onApprove)
public static AlertDialog showApprove(@NonNull Context context,
@NonNull Recipient requester,
@NonNull Runnable onApprove)
{
return new AlertDialog.Builder(context)
.setMessage(context.getString(R.string.RequestConfirmationDialog_add_s_to_the_group,
requester.getDisplayName(context)))
.setPositiveButton(R.string.RequestConfirmationDialog_add, (dialog, which) -> onApprove.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
return new MaterialAlertDialogBuilder(context)
.setMessage(context.getString(R.string.RequestConfirmationDialog_add_s_to_the_group,
requester.getDisplayName(context)))
.setPositiveButton(R.string.RequestConfirmationDialog_add, (dialog, which) -> onApprove.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
/**
* Confirms that you want to deny a request to join the group.
*/
private static AlertDialog showRequestDenyConfirmationDialog(@NonNull Context context,
@NonNull Recipient requester,
@NonNull Runnable onDeny)
public static AlertDialog showDeny(@NonNull Context context,
@NonNull Recipient requester,
boolean linkEnabled,
@NonNull Runnable onDeny)
{
return new AlertDialog.Builder(context)
.setMessage(context.getString(R.string.RequestConfirmationDialog_deny_request_from_s,
requester.getDisplayName(context)))
.setPositiveButton(R.string.RequestConfirmationDialog_deny, (dialog, which) -> onDeny.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
String message = linkEnabled ? context.getString(R.string.RequestConfirmationDialog_deny_request_from_s_they_will_not_be_able_to_request, requester.getDisplayName(context))
: context.getString(R.string.RequestConfirmationDialog_deny_request_from_s, requester.getDisplayName(context));
return new MaterialAlertDialogBuilder(context)
.setMessage(message)
.setPositiveButton(R.string.RequestConfirmationDialog_deny, (dialog, which) -> onDeny.run())
.setNegativeButton(android.R.string.cancel, null)
.show();
}
}

Wyświetl plik

@ -15,13 +15,11 @@ import org.thoughtcrime.securesms.groups.LiveGroup;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.groups.ui.GroupErrors;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.groups.v2.GroupLinkUrlAndStatus;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import org.thoughtcrime.securesms.util.SingleLiveEvent;
import java.util.Collections;
import java.util.List;
import java.util.Set;
public class RequestingMemberInvitesViewModel extends ViewModel {
@ -29,6 +27,7 @@ public class RequestingMemberInvitesViewModel extends ViewModel {
private final RequestingMemberRepository requestingMemberRepository;
private final MutableLiveData<String> toasts;
private final LiveData<List<GroupMemberEntry.RequestingMember>> requesting;
private final LiveData<GroupLinkUrlAndStatus> inviteLink;
private RequestingMemberInvitesViewModel(@NonNull Context context,
@NonNull GroupId.V2 groupId,
@ -36,62 +35,58 @@ public class RequestingMemberInvitesViewModel extends ViewModel {
{
this.context = context;
this.requestingMemberRepository = requestingMemberRepository;
this.requesting = new LiveGroup(groupId).getRequestingMembers();
this.toasts = new SingleLiveEvent<>();
LiveGroup liveGroup = new LiveGroup(groupId);
this.requesting = liveGroup.getRequestingMembers();
this.inviteLink = liveGroup.getGroupLink();
}
LiveData<List<GroupMemberEntry.RequestingMember>> getRequesting() {
return requesting;
}
LiveData<GroupLinkUrlAndStatus> getInviteLink() {
return inviteLink;
}
LiveData<String> getToasts() {
return toasts;
}
void approveRequestFor(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
approveOrDeny(requestingMember, true);
requestingMember.setBusy(true);
requestingMemberRepository.approveRequest(requestingMember.getRequester(), new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_added_s, requestingMember.getRequester().getDisplayName(context)));
}
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
}
void denyRequestFor(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
approveOrDeny(requestingMember, false);
}
requestingMember.setBusy(true);
requestingMemberRepository.denyRequest(requestingMember.getRequester(), new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_denied_s, requestingMember.getRequester().getDisplayName(context)));
}
private void approveOrDeny(@NonNull GroupMemberEntry.RequestingMember requestingMember, boolean approve) {
RequestConfirmationDialog.show(context, requestingMember.getRequester(), approve, () -> {
Set<RecipientId> memberAsSet = Collections.singleton(requestingMember.getRequester().getId());
if (approve) {
requestingMember.setBusy(true);
requestingMemberRepository.approveRequests(memberAsSet, new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_added_s, requestingMember.getRequester().getDisplayName(context)));
}
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
} else {
requestingMember.setBusy(true);
requestingMemberRepository.denyRequests(memberAsSet, new AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason>() {
@Override
public void onComplete(@Nullable Void result) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(R.string.RequestingMembersFragment_denied_s, requestingMember.getRequester().getDisplayName(context)));
}
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
}
});
@Override
public void onError(@Nullable GroupChangeFailureReason error) {
requestingMember.setBusy(false);
toasts.postValue(context.getString(GroupErrors.getUserDisplayMessage(error)));
}
});
}
public static class Factory implements ViewModelProvider.Factory {

Wyświetl plik

@ -10,11 +10,13 @@ import org.thoughtcrime.securesms.groups.GroupChangeException;
import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.AsynchronousCallback;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
/**
* Repository for modifying the requesting members on a single group.
@ -31,12 +33,12 @@ final class RequestingMemberRepository {
this.groupId = groupId;
}
void approveRequests(@NonNull Collection<RecipientId> recipientIds,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
void approveRequest(@NonNull Recipient recipient,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
{
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.approveRequests(context, groupId, recipientIds);
GroupManager.approveRequests(context, groupId, Collections.singleton(recipient.getId()));
callback.onComplete(null);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);
@ -45,12 +47,12 @@ final class RequestingMemberRepository {
});
}
void denyRequests(@NonNull Collection<RecipientId> recipientIds,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
void denyRequest(@NonNull Recipient recipient,
@NonNull AsynchronousCallback.WorkerThread<Void, GroupChangeFailureReason> callback)
{
SignalExecutors.UNBOUNDED.execute(() -> {
try {
GroupManager.denyRequests(context, groupId, recipientIds);
GroupManager.denyRequests(context, groupId, Collections.singleton(recipient.getId()));
callback.onComplete(null);
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);

Wyświetl plik

@ -9,6 +9,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import org.thoughtcrime.securesms.R;
@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.ui.AdminActionsListener;
import org.thoughtcrime.securesms.groups.ui.GroupMemberEntry;
import org.thoughtcrime.securesms.groups.ui.GroupMemberListView;
import org.thoughtcrime.securesms.groups.v2.GroupLinkUrlAndStatus;
import org.thoughtcrime.securesms.recipients.ui.bottomsheet.RecipientBottomSheetDialogFragment;
import org.thoughtcrime.securesms.util.BottomSheetUtil;
@ -29,9 +31,9 @@ public class RequestingMembersFragment extends Fragment {
private static final String GROUP_ID = "GROUP_ID";
private RequestingMemberInvitesViewModel viewModel;
private GroupMemberListView requestingMembers;
private View noRequestingMessage;
private View requestingExplanation;
private GroupMemberListView requestingMembers;
private View noRequestingMessage;
private View requestingExplanation;
public static RequestingMembersFragment newInstance(@NonNull GroupId.V2 groupId) {
RequestingMembersFragment fragment = new RequestingMembersFragment();
@ -53,9 +55,10 @@ public class RequestingMembersFragment extends Fragment {
requestingMembers.initializeAdapter(getViewLifecycleOwner());
requestingMembers.setRecipientClickListener(recipient ->
requestingMembers.setRecipientClickListener(recipient -> {
RecipientBottomSheetDialogFragment.create(recipient.getId(), null)
.show(requireActivity().getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG));
.show(requireActivity().getSupportFragmentManager(), BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
});
requestingMembers.setAdminActionsListener(new AdminActionsListener() {
@ -71,31 +74,37 @@ public class RequestingMembersFragment extends Fragment {
@Override
public void onApproveRequest(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
viewModel.approveRequestFor(requestingMember);
//noinspection CodeBlock2Expr
RequestConfirmationDialog.showApprove(requireContext(), requestingMember.getRequester(), () -> {
viewModel.approveRequestFor(requestingMember);
});
}
@Override
public void onDenyRequest(@NonNull GroupMemberEntry.RequestingMember requestingMember) {
viewModel.denyRequestFor(requestingMember);
GroupLinkUrlAndStatus linkStatus = viewModel.getInviteLink().getValue();
boolean linkEnabled = linkStatus == null || linkStatus.isEnabled();
//noinspection CodeBlock2Expr
RequestConfirmationDialog.showDeny(requireContext(), requestingMember.getRequester(), linkEnabled, () -> {
viewModel.denyRequestFor(requestingMember);
});
}
});
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
@Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
GroupId.V2 groupId = GroupId.parseOrThrow(Objects.requireNonNull(requireArguments().getString(GROUP_ID))).requireV2();
RequestingMemberInvitesViewModel.Factory factory = new RequestingMemberInvitesViewModel.Factory(requireContext(), groupId);
viewModel = ViewModelProviders.of(requireActivity(), factory).get(RequestingMemberInvitesViewModel.class);
viewModel = new ViewModelProvider(this, factory).get(RequestingMemberInvitesViewModel.class);
viewModel.getRequesting().observe(getViewLifecycleOwner(), requesting -> {
requestingMembers.setMembers(requesting);
noRequestingMessage.setVisibility(requesting.isEmpty() ? View.VISIBLE: View.GONE);
noRequestingMessage.setVisibility(requesting.isEmpty() ? View.VISIBLE : View.GONE);
requestingExplanation.setVisibility(requesting.isEmpty() ? View.GONE : View.VISIBLE);
});

Wyświetl plik

@ -101,7 +101,7 @@ class ReviewCardRepository {
SignalExecutors.BOUNDED.execute(() -> {
try {
GroupManager.ejectFromGroup(context, groupId, reviewCard.getReviewRecipient());
GroupManager.ejectAndBanFromGroup(context, groupId, reviewCard.getReviewRecipient());
onRemoveFromGroupListener.onActionCompleted();
} catch (GroupChangeException | IOException e) {
onRemoveFromGroupListener.onActionFailed();

Wyświetl plik

@ -290,6 +290,10 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
makeGroupAdminButton.setVisibility(adminStatus.isCanMakeAdmin() ? View.VISIBLE : View.GONE);
removeAdminButton.setVisibility(adminStatus.isCanMakeNonAdmin() ? View.VISIBLE : View.GONE);
removeFromGroupButton.setVisibility(adminStatus.isCanRemove() ? View.VISIBLE : View.GONE);
if (adminStatus.isCanRemove()) {
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), adminStatus.isLinkActive(), this::dismiss));
}
});
viewModel.getIdentity().observe(getViewLifecycleOwner(), identityRecord -> {
@ -319,8 +323,6 @@ public final class RecipientBottomSheetDialogFragment extends BottomSheetDialogF
makeGroupAdminButton.setOnClickListener(view -> viewModel.onMakeGroupAdminClicked(requireActivity()));
removeAdminButton.setOnClickListener(view -> viewModel.onRemoveGroupAdminClicked(requireActivity()));
removeFromGroupButton.setOnClickListener(view -> viewModel.onRemoveFromGroupClicked(requireActivity(), this::dismiss));
addToGroupButton.setOnClickListener(view -> {
dismiss();
viewModel.onAddToGroupButton(requireActivity());

Wyświetl plik

@ -77,7 +77,7 @@ final class RecipientDialogRepository {
SimpleTask.run(SignalExecutors.UNBOUNDED,
() -> {
try {
GroupManager.ejectFromGroup(context, Objects.requireNonNull(groupId).requireV2(), Recipient.resolved(recipientId));
GroupManager.ejectAndBanFromGroup(context, Objects.requireNonNull(groupId).requireV2(), Recipient.resolved(recipientId));
return true;
} catch (GroupChangeException | IOException e) {
Log.w(TAG, e);

Wyświetl plik

@ -15,6 +15,8 @@ import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.signal.core.util.ThreadUtil;
import org.thoughtcrime.securesms.BlockUnblockDialog;
import org.thoughtcrime.securesms.R;
@ -33,6 +35,7 @@ import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.util.CommunicationActions;
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
import org.thoughtcrime.securesms.verify.VerifyIdentityActivity;
import org.whispersystems.libsignal.util.Pair;
import java.util.Objects;
@ -68,20 +71,22 @@ final class RecipientDialogViewModel extends ViewModel {
if (recipientDialogRepository.getGroupId() != null && recipientDialogRepository.getGroupId().isV2() && !recipientIsSelf) {
LiveGroup source = new LiveGroup(recipientDialogRepository.getGroupId());
LiveData<Boolean> localIsAdmin = source.isSelfAdmin();
LiveData<Pair<Boolean, Boolean>> localStatus = LiveDataUtil.combineLatest(source.isSelfAdmin(), Transformations.map(source.getGroupLink(), s -> s == null || s.isEnabled()), Pair::new);
LiveData<GroupDatabase.MemberLevel> recipientMemberLevel = Transformations.switchMap(recipient, source::getMemberLevel);
adminActionStatus = LiveDataUtil.combineLatest(localIsAdmin, recipientMemberLevel,
(localAdmin, memberLevel) -> {
boolean inGroup = memberLevel.isInGroup();
boolean recipientAdmin = memberLevel == GroupDatabase.MemberLevel.ADMINISTRATOR;
adminActionStatus = LiveDataUtil.combineLatest(localStatus, recipientMemberLevel, (statuses, memberLevel) -> {
boolean localAdmin = statuses.first();
boolean isLinkActive = statuses.second();
boolean inGroup = memberLevel.isInGroup();
boolean recipientAdmin = memberLevel == GroupDatabase.MemberLevel.ADMINISTRATOR;
return new AdminActionStatus(inGroup && localAdmin,
inGroup && localAdmin && !recipientAdmin,
inGroup && localAdmin && recipientAdmin);
});
return new AdminActionStatus(inGroup && localAdmin,
inGroup && localAdmin && !recipientAdmin,
inGroup && localAdmin && recipientAdmin,
isLinkActive);
});
} else {
adminActionStatus = new MutableLiveData<>(new AdminActionStatus(false, false, false));
adminActionStatus = new MutableLiveData<>(new AdminActionStatus(false, false, false, false));
}
boolean isSelf = recipientDialogRepository.getRecipientId().equals(Recipient.self().getId());
@ -164,7 +169,7 @@ final class RecipientDialogViewModel extends ViewModel {
}
void onMakeGroupAdminClicked(@NonNull Activity activity) {
new AlertDialog.Builder(activity)
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_s_will_be_able_to_edit_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_make_admin,
(dialog, which) -> {
@ -182,7 +187,7 @@ final class RecipientDialogViewModel extends ViewModel {
}
void onRemoveGroupAdminClicked(@NonNull Activity activity) {
new AlertDialog.Builder(activity)
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_remove_s_as_group_admin, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove_as_admin,
(dialog, which) -> {
@ -199,9 +204,11 @@ final class RecipientDialogViewModel extends ViewModel {
.show();
}
void onRemoveFromGroupClicked(@NonNull Activity activity, @NonNull Runnable onSuccess) {
new AlertDialog.Builder(activity)
.setMessage(context.getString(R.string.RecipientBottomSheet_remove_s_from_the_group, Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
void onRemoveFromGroupClicked(@NonNull Activity activity, boolean isLinkActive, @NonNull Runnable onSuccess) {
new MaterialAlertDialogBuilder(activity)
.setMessage(context.getString(isLinkActive ? R.string.RecipientBottomSheet_remove_s_from_the_group_they_will_not_be_able_to_rejoin
: R.string.RecipientBottomSheet_remove_s_from_the_group,
Objects.requireNonNull(recipient.getValue()).getDisplayName(context)))
.setPositiveButton(R.string.RecipientBottomSheet_remove,
(dialog, which) -> {
adminActionBusy.setValue(true);
@ -234,11 +241,13 @@ final class RecipientDialogViewModel extends ViewModel {
private final boolean canRemove;
private final boolean canMakeAdmin;
private final boolean canMakeNonAdmin;
private final boolean isLinkActive;
AdminActionStatus(boolean canRemove, boolean canMakeAdmin, boolean canMakeNonAdmin) {
AdminActionStatus(boolean canRemove, boolean canMakeAdmin, boolean canMakeNonAdmin, boolean isLinkActive) {
this.canRemove = canRemove;
this.canMakeAdmin = canMakeAdmin;
this.canMakeNonAdmin = canMakeNonAdmin;
this.isLinkActive = isLinkActive;
}
boolean isCanRemove() {
@ -252,6 +261,10 @@ final class RecipientDialogViewModel extends ViewModel {
boolean isCanMakeNonAdmin() {
return canMakeNonAdmin;
}
boolean isLinkActive() {
return isLinkActive;
}
}
public static class Factory implements ViewModelProvider.Factory {

Wyświetl plik

@ -913,6 +913,8 @@
<!-- GV2 Request confirmation dialog -->
<string name="RequestConfirmationDialog_add_s_to_the_group">Add “%1$s” to the group?</string>
<string name="RequestConfirmationDialog_deny_request_from_s">Deny request from “%1$s”?</string>
<!-- Confirm dialog message shown when deny a group link join request and group link is enabled. -->
<string name="RequestConfirmationDialog_deny_request_from_s_they_will_not_be_able_to_request">Deny request from “%1$s”? They will not be able to request to join via the group link again.</string>
<string name="RequestConfirmationDialog_add">Add</string>
<string name="RequestConfirmationDialog_deny">Deny</string>
@ -3350,6 +3352,8 @@
<string name="RecipientBottomSheet_s_will_be_able_to_edit_group">"%1$s" will be able to edit this group and its members.</string>
<string name="RecipientBottomSheet_remove_s_from_the_group">Remove %1$s from the group?</string>
<!-- Dialog message shown when removing someone from a group with group link being active to indicate they will not be able to rejoin -->
<string name="RecipientBottomSheet_remove_s_from_the_group_they_will_not_be_able_to_rejoin">Remove %1$s from the group? They will not be able to rejoin via the group link.</string>
<string name="RecipientBottomSheet_remove">Remove</string>
<string name="RecipientBottomSheet_copied_to_clipboard">Copied to clipboard</string>

Wyświetl plik

@ -40,4 +40,8 @@ public interface ChangeSetModifier {
void clearModifyDescription();
void clearModifyAnnouncementsOnly();
void removeAddBannedMembers(int i);
void removeDeleteBannedMembers(int i);
}

Wyświetl plik

@ -133,6 +133,16 @@ final class DecryptedGroupChangeActionsBuilderChangeSetModifier implements Chang
result.clearNewIsAnnouncementGroup();
}
@Override
public void removeAddBannedMembers(int i) {
result.removeNewBannedMembers(i);
}
@Override
public void removeDeleteBannedMembers(int i) {
result.removeDeleteBannedMembers(i);
}
private static List<ByteString> removeIndexFromByteStringList(List<ByteString> byteStrings, int i) {
List<ByteString> modifiedList = new ArrayList<>(byteStrings);

Wyświetl plik

@ -5,6 +5,7 @@ import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
@ -297,6 +298,10 @@ public final class DecryptedGroupUtil {
applyInviteLinkPassword(builder, change);
applyAddBannedMembersActions(builder, change.getNewBannedMembersList());
applyDeleteBannedMembersActions(builder, change.getDeleteBannedMembersList());
return builder.build();
}
@ -505,6 +510,31 @@ public final class DecryptedGroupUtil {
}
}
private static void applyAddBannedMembersActions(DecryptedGroup.Builder builder, List<DecryptedBannedMember> newBannedMembersList) {
Set<ByteString> bannedMemberUuidSet = getBannedMemberUuidSet(builder.getBannedMembersList());
for (DecryptedBannedMember member : newBannedMembersList) {
if (bannedMemberUuidSet.contains(member.getUuid())) {
Log.w(TAG, "Banned member already in banned list");
} else {
builder.addBannedMembers(member);
}
}
}
private static void applyDeleteBannedMembersActions(DecryptedGroup.Builder builder, List<DecryptedBannedMember> deleteMembersList) {
for (DecryptedBannedMember removedMember : deleteMembersList) {
int index = indexOfUuidInBannedMemberList(builder.getBannedMembersList(), removedMember.getUuid());
if (index == -1) {
Log.w(TAG, "Deleted banned member on change not found in banned list");
continue;
}
builder.removeBannedMembers(index);
}
}
private static DecryptedMember withNewProfileKey(DecryptedMember member, ByteString profileKey) {
return DecryptedMember.newBuilder(member)
.setProfileKey(profileKey)
@ -531,6 +561,16 @@ public final class DecryptedGroupUtil {
return pendingMemberCipherTexts;
}
private static Set<ByteString> getBannedMemberUuidSet(List<DecryptedBannedMember> bannedMemberList) {
Set<ByteString> memberUuids = new HashSet<>(bannedMemberList.size());
for (DecryptedBannedMember member : bannedMemberList) {
memberUuids.add(member.getUuid());
}
return memberUuids;
}
private static void removePendingAndRequestingMembersNowInGroup(DecryptedGroup.Builder builder) {
Set<ByteString> allMembers = membersToUuidByteStringSet(builder.getMembersList());
@ -569,6 +609,13 @@ public final class DecryptedGroupUtil {
return -1;
}
private static int indexOfUuidInBannedMemberList(List<DecryptedBannedMember> memberList, ByteString uuid) {
for (int i = 0; i < memberList.size(); i++) {
if (uuid.equals(memberList.get(i).getUuid())) return i;
}
return -1;
}
public static Optional<UUID> findInviter(List<DecryptedPendingMember> pendingMembersList, UUID uuid) {
return Optional.fromNullable(findPendingByUuid(pendingMembersList, uuid).transform(DecryptedPendingMember::getAddedByUuid)
.transform(UuidUtil::fromByteStringOrNull)
@ -598,7 +645,9 @@ public final class DecryptedGroupUtil {
change.getPromoteRequestingMembersCount() == 0 && // field 18
change.getNewInviteLinkPassword().size() == 0 && // field 19
!change.hasNewDescription() && // field 20
isEmpty(change.getNewIsAnnouncementGroup()); // field 20
isEmpty(change.getNewIsAnnouncementGroup()) && // field 21
change.getNewBannedMembersCount() == 0 && // field 22
change.getDeleteBannedMembersCount() == 0; // field 23
}
static boolean isEmpty(AccessControl.AccessRequired newAttributeAccess) {

Wyświetl plik

@ -112,4 +112,14 @@ final class GroupChangeActionsBuilderChangeSetModifier implements ChangeSetModif
public void clearModifyAnnouncementsOnly() {
result.clearModifyAnnouncementsOnly();
}
@Override
public void removeAddBannedMembers(int i) {
result.removeAddBannedMembers(i);
}
@Override
public void removeDeleteBannedMembers(int i) {
result.removeDeleteBannedMembers(i);
}
}

Wyświetl plik

@ -3,6 +3,7 @@ package org.whispersystems.signalservice.api.groupsv2;
import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
@ -65,12 +66,17 @@ public final class GroupChangeReconstruct {
Set<ByteString> requestingMembersListA = requestingMembersToSetOfUuids(fromState.getRequestingMembersList());
Set<ByteString> requestingMembersListB = requestingMembersToSetOfUuids(toState.getRequestingMembersList());
Set<ByteString> bannedMembersListA = bannedMembersToSetOfUuids(fromState.getBannedMembersList());
Set<ByteString> bannedMembersListB = bannedMembersToSetOfUuids(toState.getBannedMembersList());
Set<ByteString> removedPendingMemberUuids = subtract(pendingMembersListA, pendingMembersListB);
Set<ByteString> removedRequestingMemberUuids = subtract(requestingMembersListA, requestingMembersListB);
Set<ByteString> newPendingMemberUuids = subtract(pendingMembersListB, pendingMembersListA);
Set<ByteString> newRequestingMemberUuids = subtract(requestingMembersListB, requestingMembersListA);
Set<ByteString> removedMemberUuids = subtract(fromStateMemberUuids, toStateMemberUuids);
Set<ByteString> newMemberUuids = subtract(toStateMemberUuids, fromStateMemberUuids);
Set<ByteString> removedBannedMemberUuids = subtract(bannedMembersListA, bannedMembersListB);
Set<ByteString> newBannedMemberUuids = subtract(bannedMembersListB, bannedMembersListA);
Set<ByteString> addedByInvitationUuids = intersect(newMemberUuids, removedPendingMemberUuids);
Set<ByteString> addedByRequestApprovalUuids = intersect(newMemberUuids, removedRequestingMemberUuids);
@ -141,6 +147,14 @@ public final class GroupChangeReconstruct {
builder.setNewInviteLinkPassword(toState.getInviteLinkPassword());
}
for (ByteString uuid : removedBannedMemberUuids) {
builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(uuid).build());
}
for (ByteString uuid : newBannedMemberUuids) {
builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(uuid).build());
}
return builder.build();
}
@ -203,6 +217,14 @@ public final class GroupChangeReconstruct {
return uuids;
}
private static Set<ByteString> bannedMembersToSetOfUuids(Collection<DecryptedBannedMember> bannedMembers) {
Set<ByteString> uuids = new LinkedHashSet<>(bannedMembers.size());
for (DecryptedBannedMember bannedMember : bannedMembers) {
uuids.add(bannedMember.getUuid());
}
return uuids;
}
private static <T> Set<T> subtract(Collection<T> a, Collection<T> b) {
Set<T> result = new LinkedHashSet<>(a);
result.removeAll(b);

Wyświetl plik

@ -4,6 +4,7 @@ import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
@ -43,7 +44,9 @@ public final class GroupChangeUtil {
change.getPromoteRequestingMembersCount() == 0 && // field 18
!change.hasModifyInviteLinkPassword() && // field 19
!change.hasModifyDescription() && // field 20
!change.hasModifyAnnouncementsOnly(); // field 21
!change.hasModifyAnnouncementsOnly() && // field 21
change.getAddBannedMembersCount() == 0 && // field 22
change.getDeleteBannedMembersCount() == 0; // field 23
}
/**
@ -107,6 +110,7 @@ public final class GroupChangeUtil {
HashMap<ByteString, DecryptedMember> fullMembersByUuid = new HashMap<>(groupState.getMembersCount());
HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid = new HashMap<>(groupState.getPendingMembersCount());
HashMap<ByteString, DecryptedRequestingMember> requestingMembersByUuid = new HashMap<>(groupState.getMembersCount());
HashMap<ByteString, DecryptedBannedMember> bannedMembersByUuid = new HashMap<>(groupState.getBannedMembersCount());
for (DecryptedMember member : groupState.getMembersList()) {
fullMembersByUuid.put(member.getUuid(), member);
@ -120,6 +124,10 @@ public final class GroupChangeUtil {
requestingMembersByUuid.put(member.getUuid(), member);
}
for (DecryptedBannedMember member : groupState.getBannedMembersList()) {
bannedMembersByUuid.put(member.getUuid(), member);
}
resolveField3AddMembers (conflictingChange, changeSetModifier, fullMembersByUuid, pendingMembersByUuid);
resolveField4DeleteMembers (conflictingChange, changeSetModifier, fullMembersByUuid);
resolveField5ModifyMemberRoles (conflictingChange, changeSetModifier, fullMembersByUuid);
@ -138,6 +146,8 @@ public final class GroupChangeUtil {
resolveField18PromoteRequestingMembers (conflictingChange, changeSetModifier, requestingMembersByUuid);
resolveField20ModifyDescription (groupState, conflictingChange, changeSetModifier);
resolveField21ModifyAnnouncementsOnly (groupState, conflictingChange, changeSetModifier);
resolveField22AddBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid);
resolveField23DeleteBannedMembers (conflictingChange, changeSetModifier, bannedMembersByUuid);
}
private static void resolveField3AddMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedMember> fullMembersByUuid, HashMap<ByteString, DecryptedPendingMember> pendingMembersByUuid) {
@ -319,4 +329,28 @@ public final class GroupChangeUtil {
result.clearModifyAnnouncementsOnly();
}
}
private static void resolveField22AddBannedMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedBannedMember> bannedMembersByUuid) {
List<DecryptedBannedMember> newBannedMembersList = conflictingChange.getNewBannedMembersList();
for (int i = newBannedMembersList.size() - 1; i >= 0; i--) {
DecryptedBannedMember member = newBannedMembersList.get(i);
if (bannedMembersByUuid.containsKey(member.getUuid())) {
result.removeAddBannedMembers(i);
}
}
}
private static void resolveField23DeleteBannedMembers(DecryptedGroupChange conflictingChange, ChangeSetModifier result, HashMap<ByteString, DecryptedBannedMember> bannedMembersByUuid) {
List<DecryptedBannedMember> deleteBannedMembersList = conflictingChange.getDeleteBannedMembersList();
for (int i = deleteBannedMembersList.size() - 1; i >= 0; i--) {
DecryptedBannedMember member = deleteBannedMembersList.get(i);
if (!bannedMembersByUuid.containsKey(member.getUuid())) {
result.removeDeleteBannedMembers(i);
}
}
}
}

Wyświetl plik

@ -4,6 +4,7 @@ import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.BannedMember;
import org.signal.storageservice.protos.groups.Group;
import org.signal.storageservice.protos.groups.GroupAttributeBlob;
import org.signal.storageservice.protos.groups.GroupChange;
@ -12,6 +13,7 @@ import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.PendingMember;
import org.signal.storageservice.protos.groups.RequestingMember;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedGroupJoinInfo;
@ -47,6 +49,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* Contains operations to create, modify and validate groups and group changes.
@ -59,7 +62,7 @@ public final class GroupsV2Operations {
public static final UUID UNKNOWN_UUID = UuidUtil.UNKNOWN_UUID;
/** Highest change epoch this class knows now to decrypt */
public static final int HIGHEST_KNOWN_EPOCH = 3;
public static final int HIGHEST_KNOWN_EPOCH = 4;
private final ServerPublicParams serverPublicParams;
private final ClientZkProfileOperations clientZkProfileOperations;
@ -160,7 +163,7 @@ public final class GroupsV2Operations {
public GroupChange.Actions.Builder createModifyGroupMembershipChange(Set<GroupCandidate> membersToAdd, UUID selfUuid) {
final GroupOperations groupOperations = forGroup(groupSecretParams);
GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder();
GroupChange.Actions.Builder actions = createUnbanUuidsChange(membersToAdd.stream().map(GroupCandidate::getUuid).collect(Collectors.toSet()));
for (GroupCandidate credential : membersToAdd) {
Member.Role newMemberRole = Member.Role.DEFAULT;
@ -203,8 +206,9 @@ public final class GroupsV2Operations {
return actions;
}
public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set<UUID> requestsToRemove) {
GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder();
public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set<UUID> requestsToRemove, boolean alsoBan) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(requestsToRemove)
: GroupChange.Actions.newBuilder();
for (UUID uuid : requestsToRemove) {
actions.addDeleteRequestingMembers(GroupChange.Actions.DeleteRequestingMemberAction
@ -228,8 +232,9 @@ public final class GroupsV2Operations {
return actions;
}
public GroupChange.Actions.Builder createRemoveMembersChange(final Set<UUID> membersToRemove) {
GroupChange.Actions.Builder actions = GroupChange.Actions.newBuilder();
public GroupChange.Actions.Builder createRemoveMembersChange(final Set<UUID> membersToRemove, boolean alsoBan) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(membersToRemove)
: GroupChange.Actions.newBuilder();
for (UUID remove: membersToRemove) {
actions.addDeleteMembers(GroupChange.Actions.DeleteMemberAction
@ -241,7 +246,7 @@ public final class GroupsV2Operations {
}
public GroupChange.Actions.Builder createLeaveAndPromoteMembersToAdmin(UUID self, List<UUID> membersToMakeAdmin) {
GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self));
GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self), false);
for (UUID member : membersToMakeAdmin) {
actions.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction
@ -342,6 +347,26 @@ public final class GroupsV2Operations {
.setAnnouncementsOnly(isAnnouncementGroup));
}
public GroupChange.Actions.Builder createBanUuidsChange(Set<UUID> banUuids) {
GroupChange.Actions.Builder builder = GroupChange.Actions.newBuilder();
for (UUID uuid : banUuids) {
builder.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encryptUuid(uuid)).build()));
}
return builder;
}
public GroupChange.Actions.Builder createUnbanUuidsChange(Set<UUID> banUuids) {
GroupChange.Actions.Builder builder = GroupChange.Actions.newBuilder();
for (UUID uuid : banUuids) {
builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptUuid(uuid)).build());
}
return builder;
}
private Member.Builder member(ProfileKeyCredential credential, Member.Role role) {
ProfileKeyCredentialPresentation presentation = clientZkProfileOperations.createProfileKeyCredentialPresentation(new SecureRandom(), groupSecretParams, credential);
@ -410,6 +435,7 @@ public final class GroupsV2Operations {
List<DecryptedMember> decryptedMembers = new ArrayList<>(membersList.size());
List<DecryptedPendingMember> decryptedPendingMembers = new ArrayList<>(pendingMembersList.size());
List<DecryptedRequestingMember> decryptedRequestingMembers = new ArrayList<>(requestingMembersList.size());
List<DecryptedBannedMember> decryptedBannedMembers = new ArrayList<>(group.getBannedMembersCount());
for (Member member : membersList) {
try {
@ -427,6 +453,10 @@ public final class GroupsV2Operations {
decryptedRequestingMembers.add(decryptRequestingMember(member));
}
for (BannedMember member : group.getBannedMembersList()) {
decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(member.getUserId())).build());
}
return DecryptedGroup.newBuilder()
.setTitle(decryptTitle(group.getTitle()))
.setDescription(decryptDescription(group.getDescription()))
@ -439,6 +469,7 @@ public final class GroupsV2Operations {
.addAllRequestingMembers(decryptedRequestingMembers)
.setDisappearingMessagesTimer(DecryptedTimer.newBuilder().setDuration(decryptDisappearingMessagesTimer(group.getDisappearingMessagesTimer())))
.setInviteLinkPassword(group.getInviteLinkPassword())
.addAllBannedMembers(decryptedBannedMembers)
.build();
}
@ -625,6 +656,16 @@ public final class GroupsV2Operations {
builder.setNewIsAnnouncementGroup(actions.getModifyAnnouncementsOnly().getAnnouncementsOnly() ? EnabledState.ENABLED : EnabledState.DISABLED);
}
// Field 22
for (GroupChange.Actions.AddBannedMemberAction action : actions.getAddBannedMembersList()) {
builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getAdded().getUserId())).build());
}
// Field 23
for (GroupChange.Actions.DeleteBannedMemberAction action : actions.getDeleteBannedMembersList()) {
builder.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getDeletedUserId())).build());
}
return builder.build();
}

Wyświetl plik

@ -33,6 +33,10 @@ message DecryptedRequestingMember {
uint64 timestamp = 4;
}
message DecryptedBannedMember {
bytes uuid = 1;
}
message DecryptedPendingMemberRemoval {
bytes uuid = 1;
bytes uuidCipherText = 2;
@ -62,6 +66,7 @@ message DecryptedGroup {
bytes inviteLinkPassword = 10;
string description = 11;
EnabledState isAnnouncementGroup = 12;
repeated DecryptedBannedMember bannedMembers = 13;
}
// Decrypted version of message GroupChange.Actions
@ -88,6 +93,8 @@ message DecryptedGroupChange {
bytes newInviteLinkPassword = 19;
DecryptedString newDescription = 20;
EnabledState newIsAnnouncementGroup = 21;
repeated DecryptedBannedMember newBannedMembers = 22;
repeated DecryptedBannedMember deleteBannedMembers = 23;
}
message DecryptedString {

Wyświetl plik

@ -45,6 +45,10 @@ message RequestingMember {
uint64 timestamp = 4;
}
message BannedMember {
bytes userId = 1;
}
message AccessControl {
enum AccessRequired {
UNKNOWN = 0;
@ -72,6 +76,7 @@ message Group {
bytes inviteLinkPassword = 10;
bytes description = 11;
bool announcementsOnly = 12;
repeated BannedMember bannedMembers = 13;
}
message GroupChange {
@ -121,6 +126,14 @@ message GroupChange {
Member.Role role = 2;
}
message AddBannedMemberAction {
BannedMember added = 1;
}
message DeleteBannedMemberAction {
bytes deletedUserId = 1;
}
message ModifyTitleAction {
bytes title = 1;
}
@ -178,6 +191,8 @@ message GroupChange {
ModifyInviteLinkPasswordAction modifyInviteLinkPassword = 19;
ModifyDescriptionAction modifyDescription = 20;
ModifyAnnouncementsOnlyAction modifyAnnouncementsOnly = 21;
repeated AddBannedMemberAction addBannedMembers = 22;
repeated DeleteBannedMemberAction deleteBannedMembers = 23;
}
bytes actions = 1;

Wyświetl plik

@ -6,6 +6,7 @@ import org.junit.Test;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
@ -26,6 +27,7 @@ import static org.junit.Assert.assertEquals;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.asAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.asMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.bannedMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.newProfileKey;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
@ -46,7 +48,7 @@ public final class DecryptedGroupUtil_apply_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
@Test
@ -885,4 +887,75 @@ public final class DecryptedGroupUtil_apply_Test {
newGroup);
}
@Test
public void apply_new_banned_member() throws NotAbleToApplyGroupV2ChangeException {
DecryptedMember member1 = member(UUID.randomUUID());
DecryptedBannedMember banned = bannedMember(UUID.randomUUID());
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
.setRevision(10)
.addMembers(member1)
.build(),
DecryptedGroupChange.newBuilder()
.setRevision(11)
.addNewBannedMembers(banned)
.build());
assertEquals(DecryptedGroup.newBuilder()
.setRevision(11)
.addMembers(member1)
.addBannedMembers(banned)
.build(),
newGroup);
}
@Test
public void apply_new_banned_member_already_banned() throws NotAbleToApplyGroupV2ChangeException {
DecryptedMember member1 = member(UUID.randomUUID());
DecryptedBannedMember banned = bannedMember(UUID.randomUUID());
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
.setRevision(10)
.addMembers(member1)
.addBannedMembers(banned)
.build(),
DecryptedGroupChange.newBuilder()
.setRevision(11)
.addNewBannedMembers(banned)
.build());
assertEquals(DecryptedGroup.newBuilder()
.setRevision(11)
.addMembers(member1)
.addBannedMembers(banned)
.build(),
newGroup);
}
@Test
public void remove_banned_member() throws NotAbleToApplyGroupV2ChangeException {
DecryptedMember member1 = member(UUID.randomUUID());
UUID bannedUuid = UUID.randomUUID();
DecryptedBannedMember banned = bannedMember(bannedUuid);
DecryptedGroup newGroup = DecryptedGroupUtil.apply(DecryptedGroup.newBuilder()
.setRevision(10)
.addMembers(member1)
.addBannedMembers(banned)
.build(),
DecryptedGroupChange.newBuilder()
.setRevision(11)
.addDeleteBannedMembers(DecryptedBannedMember.newBuilder()
.setUuid(UuidUtil.toByteString(bannedUuid))
.build())
.build());
assertEquals(DecryptedGroup.newBuilder()
.setRevision(11)
.addMembers(member1)
.build(),
newGroup);
}
}

Wyświetl plik

@ -5,6 +5,7 @@ import com.google.protobuf.ByteString;
import org.junit.Test;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedRequestingMember;
import org.signal.storageservice.protos.groups.local.DecryptedString;
@ -36,7 +37,7 @@ public final class DecryptedGroupUtil_empty_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("DecryptedGroupUtil and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
@Test
@ -233,4 +234,24 @@ public final class DecryptedGroupUtil_empty_Test {
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
@Test
public void not_empty_with_add_banned_member_field_22() {
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
.addNewBannedMembers(DecryptedBannedMember.getDefaultInstance())
.build();
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
@Test
public void not_empty_with_delete_banned_member_field_23() {
DecryptedGroupChange change = DecryptedGroupChange.newBuilder()
.addDeleteBannedMembers(DecryptedBannedMember.getDefaultInstance())
.build();
assertFalse(DecryptedGroupUtil.changeIsEmpty(change));
assertFalse(DecryptedGroupUtil.changeIsEmptyExceptForProfileKeyChanges(change));
}
}

Wyświetl plik

@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.approveAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.approveMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.bannedMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.demoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.newProfileKey;
@ -42,7 +43,7 @@ public final class GroupChangeReconstructTest {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeReconstruct and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
12, maxFieldFound);
13, maxFieldFound);
}
@Test
@ -381,4 +382,26 @@ public final class GroupChangeReconstructTest {
.build(),
decryptedGroupChange);
}
@Test
public void new_banned_member() {
UUID uuidNew = UUID.randomUUID();
DecryptedGroup from = DecryptedGroup.newBuilder().build();
DecryptedGroup to = DecryptedGroup.newBuilder().addBannedMembers(bannedMember(uuidNew)).build();
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
assertEquals(DecryptedGroupChange.newBuilder().addNewBannedMembers(bannedMember(uuidNew)).build(), decryptedGroupChange);
}
@Test
public void removed_banned_member() {
UUID uuidOld = UUID.randomUUID();
DecryptedGroup from = DecryptedGroup.newBuilder().addBannedMembers(bannedMember(uuidOld)).build();
DecryptedGroup to = DecryptedGroup.newBuilder().build();
DecryptedGroupChange decryptedGroupChange = GroupChangeReconstruct.reconstructGroupChange(from, to);
assertEquals(DecryptedGroupChange.newBuilder().addDeleteBannedMembers(bannedMember(uuidOld)).build(), decryptedGroupChange);
}
}

Wyświetl plik

@ -20,7 +20,7 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(GroupChange.Actions.class);
assertEquals("GroupChangeUtil and its tests need updating to account for new fields on " + GroupChange.Actions.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
@Test
@ -198,4 +198,22 @@ public final class GroupChangeUtil_changeIsEmpty_Test {
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_add_banned_member_field_22() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.getDefaultInstance())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
@Test
public void not_empty_with_delete_banned_member_field_23() {
GroupChange.Actions actions = GroupChange.Actions.newBuilder()
.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.getDefaultInstance())
.build();
assertFalse(GroupChangeUtil.changeIsEmpty(actions));
}
}

Wyświetl plik

@ -4,6 +4,7 @@ import com.google.protobuf.ByteString;
import org.junit.Test;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.BannedMember;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.PendingMember;
@ -22,6 +23,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.approveMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.bannedMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.demoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.encrypt;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.encryptedMember;
@ -47,7 +49,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
/**
@ -60,7 +62,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + GroupChange.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
/**
@ -73,7 +75,7 @@ public final class GroupChangeUtil_resolveConflict_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
12, maxFieldFound);
13, maxFieldFound);
}
@ -741,4 +743,63 @@ public final class GroupChangeUtil_resolveConflict_Test {
assertTrue(GroupChangeUtil.changeIsEmpty(resolvedActions));
}
@Test
public void field_22__add_banned_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addBannedMembers(bannedMember(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewBannedMembers(bannedMember(member1))
.addNewBannedMembers(bannedMember(member2))
.addNewBannedMembers(bannedMember(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encrypt(member1))))
.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encrypt(member2))))
.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encrypt(member3))))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encrypt(member1))))
.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encrypt(member2))))
.build();
assertEquals(expected, resolvedActions);
}
@Test
public void field_23__delete_banned_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addBannedMembers(bannedMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeleteBannedMembers(bannedMember(member1))
.addDeleteBannedMembers(bannedMember(member2))
.addDeleteBannedMembers(bannedMember(member3))
.build();
GroupChange.Actions change = GroupChange.Actions.newBuilder()
.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encrypt(member1)))
.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encrypt(member3)))
.build();
GroupChange.Actions resolvedActions = GroupChangeUtil.resolveConflict(groupState, decryptedChange, change).build();
GroupChange.Actions expected = GroupChange.Actions.newBuilder()
.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encrypt(member2)))
.build();
assertEquals(expected, resolvedActions);
}
}

Wyświetl plik

@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.admin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.approveMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.bannedMember;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.demoteAdmin;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.member;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.pendingMember;
@ -40,7 +41,7 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
/**
@ -53,7 +54,7 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroup.class);
assertEquals("GroupChangeUtil#resolveConflict and its tests need updating to account for new fields on " + DecryptedGroup.class.getName(),
12, maxFieldFound);
13, maxFieldFound);
}
@ -600,4 +601,53 @@ public final class GroupChangeUtil_resolveConflict_decryptedOnly_Test {
assertTrue(DecryptedGroupUtil.changeIsEmpty(resolvedChanges));
}
@Test
public void field_22__add_banned_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addBannedMembers(bannedMember(member3))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addNewBannedMembers(bannedMember(member1))
.addNewBannedMembers(bannedMember(member2))
.addNewBannedMembers(bannedMember(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addNewBannedMembers(bannedMember(member1))
.addNewBannedMembers(bannedMember(member2))
.build();
assertEquals(expected, resolvedChanges);
}
@Test
public void field_23__delete_banned_members() {
UUID member1 = UUID.randomUUID();
UUID member2 = UUID.randomUUID();
UUID member3 = UUID.randomUUID();
DecryptedGroup groupState = DecryptedGroup.newBuilder()
.addMembers(member(member1))
.addBannedMembers(bannedMember(member2))
.build();
DecryptedGroupChange decryptedChange = DecryptedGroupChange.newBuilder()
.addDeleteBannedMembers(bannedMember(member1))
.addDeleteBannedMembers(bannedMember(member2))
.addDeleteBannedMembers(bannedMember(member3))
.build();
DecryptedGroupChange resolvedChanges = GroupChangeUtil.resolveConflict(groupState, decryptedChange).build();
DecryptedGroupChange expected = DecryptedGroupChange.newBuilder()
.addDeleteBannedMembers(bannedMember(member2))
.build();
assertEquals(expected, resolvedChanges);
}
}

Wyświetl plik

@ -9,6 +9,7 @@ import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
@ -65,7 +66,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(DecryptedGroupChange.class);
assertEquals("GroupV2Operations#decryptChange and its tests need updating to account for new fields on " + DecryptedGroupChange.class.getName(),
21, maxFieldFound);
23, maxFieldFound);
}
@Test
@ -102,7 +103,8 @@ public final class GroupsV2Operations_decrypt_change_Test {
.setRole(Member.Role.DEFAULT)
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.setJoinedAtRevision(10)
.setUuid(UuidUtil.toByteString(newMember))));
.setUuid(UuidUtil.toByteString(newMember)))
.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newMember)).build()));
}
@Test
@ -137,7 +139,8 @@ public final class GroupsV2Operations_decrypt_change_Test {
.setRole(Member.Role.DEFAULT)
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
.setJoinedAtRevision(10)
.setUuid(UuidUtil.toByteString(newMember))));
.setUuid(UuidUtil.toByteString(newMember)))
.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newMember)).build()));
}
@Test(expected = InvalidGroupStateException.class)
@ -156,7 +159,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
public void can_decrypt_member_removals_field4() {
UUID oldMember = UUID.randomUUID();
assertDecryption(groupOperations.createRemoveMembersChange(Collections.singleton(oldMember))
assertDecryption(groupOperations.createRemoveMembersChange(Collections.singleton(oldMember), false)
.setRevision(10),
DecryptedGroupChange.newBuilder()
.setRevision(10)
@ -227,7 +230,8 @@ public final class GroupsV2Operations_decrypt_change_Test {
.setAddedByUuid(UuidUtil.toByteString(self))
.setUuidCipherText(groupOperations.encryptUuid(newMember))
.setRole(Member.Role.DEFAULT)
.setUuid(UuidUtil.toByteString(newMember))));
.setUuid(UuidUtil.toByteString(newMember)))
.addDeleteBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newMember)).build()));
}
@Test
@ -340,11 +344,12 @@ public final class GroupsV2Operations_decrypt_change_Test {
public void can_decrypt_member_requests_refusals_field17() {
UUID newRequestingMember = UUID.randomUUID();
assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember))
assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember), true)
.setRevision(10),
DecryptedGroupChange.newBuilder()
.setRevision(10)
.addDeleteRequestingMembers(UuidUtil.toByteString(newRequestingMember)));
.addDeleteRequestingMembers(UuidUtil.toByteString(newRequestingMember))
.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(newRequestingMember)).build()));
}
@Test
@ -387,6 +392,30 @@ public final class GroupsV2Operations_decrypt_change_Test {
.setNewIsAnnouncementGroup(EnabledState.ENABLED));
}
@Test
public void can_decrypt_member_bans_field22() {
UUID ban = UUID.randomUUID();
assertDecryption(groupOperations.createBanUuidsChange(Collections.singleton(ban))
.setRevision(13),
DecryptedGroupChange.newBuilder()
.setRevision(13)
.addNewBannedMembers(DecryptedBannedMember.newBuilder()
.setUuid(UuidUtil.toByteString(ban))));
}
@Test
public void can_decrypt_banned_member_removals_field23() {
UUID ban = UUID.randomUUID();
assertDecryption(groupOperations.createUnbanUuidsChange(Collections.singleton(ban))
.setRevision(13),
DecryptedGroupChange.newBuilder()
.setRevision(13)
.addDeleteBannedMembers(DecryptedBannedMember.newBuilder()
.setUuid(UuidUtil.toByteString(ban))));
}
private static ProfileKey newProfileKey() {
try {
return new ProfileKey(Util.getSecretBytes(32));

Wyświetl plik

@ -6,12 +6,14 @@ import com.google.protobuf.InvalidProtocolBufferException;
import org.junit.Before;
import org.junit.Test;
import org.signal.storageservice.protos.groups.AccessControl;
import org.signal.storageservice.protos.groups.BannedMember;
import org.signal.storageservice.protos.groups.Group;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.PendingMember;
import org.signal.storageservice.protos.groups.RequestingMember;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.signal.storageservice.protos.groups.local.DecryptedGroupChange;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
@ -75,7 +77,7 @@ public final class GroupsV2Operations_decrypt_group_Test {
int maxFieldFound = getMaxDeclaredFieldNumber(Group.class);
assertEquals("GroupOperations and its tests need updating to account for new fields on " + Group.class.getName(),
12, maxFieldFound);
13, maxFieldFound);
}
@Test
@ -295,6 +297,20 @@ public final class GroupsV2Operations_decrypt_group_Test {
assertEquals(EnabledState.ENABLED, decryptedGroup.getIsAnnouncementGroup());
}
@Test
public void decrypt_banned_members_field_13() throws VerificationFailedException, InvalidGroupStateException {
UUID member1 = UUID.randomUUID();
Group group = Group.newBuilder()
.addBannedMembers(BannedMember.newBuilder().setUserId(groupOperations.encryptUuid(member1)))
.build();
DecryptedGroup decryptedGroup = groupOperations.decryptGroup(group);
assertEquals(1, decryptedGroup.getBannedMembersCount());
assertEquals(DecryptedBannedMember.newBuilder().setUuid(UuidUtil.toByteString(member1)).build(), decryptedGroup.getBannedMembers(0));
}
private ByteString encryptProfileKey(UUID uuid, ProfileKey profileKey) {
return ByteString.copyFrom(new ClientZkGroupCipher(groupSecretParams).encryptProfileKey(profileKey, uuid).serialize());
}

Wyświetl plik

@ -5,6 +5,7 @@ import com.google.protobuf.ByteString;
import org.signal.storageservice.protos.groups.Member;
import org.signal.storageservice.protos.groups.RequestingMember;
import org.signal.storageservice.protos.groups.local.DecryptedApproveMember;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.storageservice.protos.groups.local.DecryptedMember;
import org.signal.storageservice.protos.groups.local.DecryptedModifyMemberRole;
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
@ -122,6 +123,12 @@ final class ProtoTestUtils {
.build();
}
static DecryptedBannedMember bannedMember(UUID uuid) {
return DecryptedBannedMember.newBuilder()
.setUuid(UuidUtil.toByteString(uuid))
.build();
}
static DecryptedApproveMember approveMember(UUID uuid) {
return approve(uuid, Member.Role.DEFAULT);
}