mirror of
https://github.com/AsamK/signal-cli
synced 2025-08-29 10:30:38 +00:00
Implement add/remove admin privileges
This commit is contained in:
parent
b972522d74
commit
3de30e166f
7 changed files with 104 additions and 15 deletions
|
@ -831,6 +831,8 @@ public class Manager implements Closeable {
|
||||||
String description,
|
String description,
|
||||||
List<String> members,
|
List<String> members,
|
||||||
List<String> removeMembers,
|
List<String> removeMembers,
|
||||||
|
List<String> admins,
|
||||||
|
List<String> removeAdmins,
|
||||||
File avatarFile
|
File avatarFile
|
||||||
) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
|
) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
|
||||||
return updateGroup(groupId,
|
return updateGroup(groupId,
|
||||||
|
@ -838,21 +840,32 @@ public class Manager implements Closeable {
|
||||||
description,
|
description,
|
||||||
members == null ? null : getSignalServiceAddresses(members),
|
members == null ? null : getSignalServiceAddresses(members),
|
||||||
removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
|
removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
|
||||||
|
admins == null ? null : getSignalServiceAddresses(admins),
|
||||||
|
removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
|
||||||
avatarFile);
|
avatarFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pair<Long, List<SendMessageResult>> updateGroup(
|
private Pair<Long, List<SendMessageResult>> updateGroup(
|
||||||
GroupId groupId,
|
final GroupId groupId,
|
||||||
String name,
|
final String name,
|
||||||
String description,
|
final String description,
|
||||||
Set<RecipientId> members,
|
final Set<RecipientId> members,
|
||||||
final Set<RecipientId> removeMembers,
|
final Set<RecipientId> removeMembers,
|
||||||
File avatarFile
|
final Set<RecipientId> admins,
|
||||||
|
final Set<RecipientId> removeAdmins,
|
||||||
|
final File avatarFile
|
||||||
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
|
) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
|
||||||
var group = getGroupForUpdating(groupId);
|
var group = getGroupForUpdating(groupId);
|
||||||
|
|
||||||
if (group instanceof GroupInfoV2) {
|
if (group instanceof GroupInfoV2) {
|
||||||
return updateGroupV2((GroupInfoV2) group, name, description, members, removeMembers, avatarFile);
|
return updateGroupV2((GroupInfoV2) group,
|
||||||
|
name,
|
||||||
|
description,
|
||||||
|
members,
|
||||||
|
removeMembers,
|
||||||
|
admins,
|
||||||
|
removeAdmins,
|
||||||
|
avatarFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateGroupV1((GroupInfoV1) group, name, members, avatarFile);
|
return updateGroupV1((GroupInfoV1) group, name, members, avatarFile);
|
||||||
|
@ -913,6 +926,8 @@ public class Manager implements Closeable {
|
||||||
final String description,
|
final String description,
|
||||||
final Set<RecipientId> members,
|
final Set<RecipientId> members,
|
||||||
final Set<RecipientId> removeMembers,
|
final Set<RecipientId> removeMembers,
|
||||||
|
final Set<RecipientId> admins,
|
||||||
|
final Set<RecipientId> removeAdmins,
|
||||||
final File avatarFile
|
final File avatarFile
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
Pair<Long, List<SendMessageResult>> result = null;
|
Pair<Long, List<SendMessageResult>> result = null;
|
||||||
|
@ -947,6 +962,33 @@ public class Manager implements Closeable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (admins != null) {
|
||||||
|
final var newAdmins = new HashSet<>(admins);
|
||||||
|
newAdmins.retainAll(group.getMembers());
|
||||||
|
newAdmins.removeAll(group.getAdminMembers());
|
||||||
|
if (newAdmins.size() > 0) {
|
||||||
|
for (var admin : newAdmins) {
|
||||||
|
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, true);
|
||||||
|
result = sendUpdateGroupV2Message(group,
|
||||||
|
groupGroupChangePair.first(),
|
||||||
|
groupGroupChangePair.second());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (removeAdmins != null) {
|
||||||
|
final var existingRemoveAdmins = new HashSet<>(removeAdmins);
|
||||||
|
existingRemoveAdmins.retainAll(group.getAdminMembers());
|
||||||
|
if (existingRemoveAdmins.size() > 0) {
|
||||||
|
for (var admin : existingRemoveAdmins) {
|
||||||
|
var groupGroupChangePair = groupV2Helper.setMemberAdmin(group, admin, false);
|
||||||
|
result = sendUpdateGroupV2Message(group,
|
||||||
|
groupGroupChangePair.first(),
|
||||||
|
groupGroupChangePair.second());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result == null || name != null || description != null || avatarFile != null) {
|
if (result == null || name != null || description != null || avatarFile != null) {
|
||||||
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
|
var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
|
||||||
if (avatarFile != null) {
|
if (avatarFile != null) {
|
||||||
|
|
|
@ -227,8 +227,7 @@ public class GroupV2Helper {
|
||||||
public Pair<DecryptedGroup, GroupChange> addMembers(
|
public Pair<DecryptedGroup, GroupChange> addMembers(
|
||||||
GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers
|
GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
|
||||||
|
|
||||||
if (!areMembersValid(newMembers)) {
|
if (!areMembersValid(newMembers)) {
|
||||||
throw new IOException("Failed to update group");
|
throw new IOException("Failed to update group");
|
||||||
|
@ -318,8 +317,7 @@ public class GroupV2Helper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
|
public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
|
||||||
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
|
||||||
|
|
||||||
final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
|
final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
|
||||||
final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
|
final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
|
||||||
|
@ -337,11 +335,25 @@ public class GroupV2Helper {
|
||||||
return commitChange(groupInfoV2, change);
|
return commitChange(groupInfoV2, change);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Pair<DecryptedGroup, GroupChange> setMemberAdmin(
|
||||||
|
GroupInfoV2 groupInfoV2, RecipientId recipientId, boolean admin
|
||||||
|
) throws IOException {
|
||||||
|
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
|
final var address = addressResolver.resolveSignalServiceAddress(recipientId);
|
||||||
|
final var newRole = admin ? Member.Role.ADMINISTRATOR : Member.Role.DEFAULT;
|
||||||
|
final var change = groupOperations.createChangeMemberRole(address.getUuid().get(), newRole);
|
||||||
|
return commitChange(groupInfoV2, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupsV2Operations.GroupOperations getGroupOperations(final GroupInfoV2 groupInfoV2) {
|
||||||
|
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
||||||
|
return groupsV2Operations.forGroup(groupSecretParams);
|
||||||
|
}
|
||||||
|
|
||||||
private Pair<DecryptedGroup, GroupChange> revokeInvites(
|
private Pair<DecryptedGroup, GroupChange> revokeInvites(
|
||||||
GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
|
GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
|
||||||
final var uuidCipherTexts = pendingMembers.stream().map(member -> {
|
final var uuidCipherTexts = pendingMembers.stream().map(member -> {
|
||||||
try {
|
try {
|
||||||
return new UuidCiphertext(member.getUuidCipherText().toByteArray());
|
return new UuidCiphertext(member.getUuidCipherText().toByteArray());
|
||||||
|
@ -355,8 +367,7 @@ public class GroupV2Helper {
|
||||||
private Pair<DecryptedGroup, GroupChange> ejectMembers(
|
private Pair<DecryptedGroup, GroupChange> ejectMembers(
|
||||||
GroupInfoV2 groupInfoV2, Set<UUID> uuids
|
GroupInfoV2 groupInfoV2, Set<UUID> uuids
|
||||||
) throws IOException {
|
) throws IOException {
|
||||||
final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
|
final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
|
||||||
final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
|
|
||||||
return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(uuids));
|
return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(uuids));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,10 @@ public abstract class GroupInfo {
|
||||||
return Set.of();
|
return Set.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<RecipientId> getAdminMembers() {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
|
||||||
public abstract boolean isBlocked();
|
public abstract boolean isBlocked();
|
||||||
|
|
||||||
public abstract void setBlocked(boolean blocked);
|
public abstract void setBlocked(boolean blocked);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
import org.asamk.signal.manager.storage.recipients.RecipientId;
|
||||||
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
import org.asamk.signal.manager.storage.recipients.RecipientResolver;
|
||||||
import org.signal.storageservice.protos.groups.AccessControl;
|
import org.signal.storageservice.protos.groups.AccessControl;
|
||||||
|
import org.signal.storageservice.protos.groups.Member;
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||||
|
@ -116,6 +117,19 @@ public class GroupInfoV2 extends GroupInfo {
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<RecipientId> getAdminMembers() {
|
||||||
|
if (this.group == null) {
|
||||||
|
return Set.of();
|
||||||
|
}
|
||||||
|
return group.getMembersList()
|
||||||
|
.stream()
|
||||||
|
.filter(m -> m.getRole() == Member.Role.ADMINISTRATOR)
|
||||||
|
.map(m -> new SignalServiceAddress(UuidUtil.parseOrThrow(m.getUuid().toByteArray()), null))
|
||||||
|
.map(recipientResolver::resolveRecipient)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isBlocked() {
|
public boolean isBlocked() {
|
||||||
return blocked;
|
return blocked;
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
final var groupInviteLink = group.getGroupInviteLink();
|
final var groupInviteLink = group.getGroupInviteLink();
|
||||||
|
|
||||||
writer.println(
|
writer.println(
|
||||||
"Id: {} Name: {} Description: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Link: {}",
|
"Id: {} Name: {} Description: {} Active: {} Blocked: {} Members: {} Pending members: {} Requesting members: {} Admins: {} Link: {}",
|
||||||
group.getGroupId().toBase64(),
|
group.getGroupId().toBase64(),
|
||||||
group.getTitle(),
|
group.getTitle(),
|
||||||
group.getDescription(),
|
group.getDescription(),
|
||||||
|
@ -47,6 +47,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
resolveMembers(m, group.getMembers()),
|
resolveMembers(m, group.getMembers()),
|
||||||
resolveMembers(m, group.getPendingMembers()),
|
resolveMembers(m, group.getPendingMembers()),
|
||||||
resolveMembers(m, group.getRequestingMembers()),
|
resolveMembers(m, group.getRequestingMembers()),
|
||||||
|
resolveMembers(m, group.getAdminMembers()),
|
||||||
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
|
groupInviteLink == null ? '-' : groupInviteLink.getUrl());
|
||||||
} else {
|
} else {
|
||||||
writer.println("Id: {} Name: {} Active: {} Blocked: {}",
|
writer.println("Id: {} Name: {} Active: {} Blocked: {}",
|
||||||
|
@ -88,6 +89,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
resolveMembers(m, group.getMembers()),
|
resolveMembers(m, group.getMembers()),
|
||||||
resolveMembers(m, group.getPendingMembers()),
|
resolveMembers(m, group.getPendingMembers()),
|
||||||
resolveMembers(m, group.getRequestingMembers()),
|
resolveMembers(m, group.getRequestingMembers()),
|
||||||
|
resolveMembers(m, group.getAdminMembers()),
|
||||||
groupInviteLink == null ? null : groupInviteLink.getUrl()));
|
groupInviteLink == null ? null : groupInviteLink.getUrl()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,6 +114,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
public Set<String> members;
|
public Set<String> members;
|
||||||
public Set<String> pendingMembers;
|
public Set<String> pendingMembers;
|
||||||
public Set<String> requestingMembers;
|
public Set<String> requestingMembers;
|
||||||
|
public Set<String> admins;
|
||||||
public String groupInviteLink;
|
public String groupInviteLink;
|
||||||
|
|
||||||
public JsonGroup(
|
public JsonGroup(
|
||||||
|
@ -123,6 +126,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
Set<String> members,
|
Set<String> members,
|
||||||
Set<String> pendingMembers,
|
Set<String> pendingMembers,
|
||||||
Set<String> requestingMembers,
|
Set<String> requestingMembers,
|
||||||
|
Set<String> admins,
|
||||||
String groupInviteLink
|
String groupInviteLink
|
||||||
) {
|
) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
@ -134,6 +138,7 @@ public class ListGroupsCommand implements LocalCommand {
|
||||||
this.members = members;
|
this.members = members;
|
||||||
this.pendingMembers = pendingMembers;
|
this.pendingMembers = pendingMembers;
|
||||||
this.requestingMembers = requestingMembers;
|
this.requestingMembers = requestingMembers;
|
||||||
|
this.admins = admins;
|
||||||
this.groupInviteLink = groupInviteLink;
|
this.groupInviteLink = groupInviteLink;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,11 @@ public class UpdateGroupCommand implements DbusCommand, LocalCommand {
|
||||||
subparser.addArgument("-r", "--remove-member")
|
subparser.addArgument("-r", "--remove-member")
|
||||||
.nargs("*")
|
.nargs("*")
|
||||||
.help("Specify one or more members to remove from the group");
|
.help("Specify one or more members to remove from the group");
|
||||||
|
subparser.addArgument("--admin").nargs("*").help("Specify one or more members to make a group admin");
|
||||||
|
subparser.addArgument("--remove-admin")
|
||||||
|
.nargs("*")
|
||||||
|
.help("Specify one or more members to remove group admin privileges");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -64,6 +69,10 @@ public class UpdateGroupCommand implements DbusCommand, LocalCommand {
|
||||||
|
|
||||||
List<String> groupRemoveMembers = ns.getList("remove-member");
|
List<String> groupRemoveMembers = ns.getList("remove-member");
|
||||||
|
|
||||||
|
List<String> groupAdmins = ns.getList("admin");
|
||||||
|
|
||||||
|
List<String> groupRemoveAdmins = ns.getList("remove-admin");
|
||||||
|
|
||||||
var groupAvatar = ns.getString("avatar");
|
var groupAvatar = ns.getString("avatar");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -80,6 +89,8 @@ public class UpdateGroupCommand implements DbusCommand, LocalCommand {
|
||||||
groupDescription,
|
groupDescription,
|
||||||
groupMembers,
|
groupMembers,
|
||||||
groupRemoveMembers,
|
groupRemoveMembers,
|
||||||
|
groupAdmins,
|
||||||
|
groupRemoveAdmins,
|
||||||
groupAvatar == null ? null : new File(groupAvatar));
|
groupAvatar == null ? null : new File(groupAvatar));
|
||||||
ErrorUtils.handleTimestampAndSendMessageResults(writer, results.first(), results.second());
|
ErrorUtils.handleTimestampAndSendMessageResults(writer, results.first(), results.second());
|
||||||
}
|
}
|
||||||
|
|
|
@ -345,6 +345,8 @@ public class DbusSignalImpl implements Signal {
|
||||||
null,
|
null,
|
||||||
members,
|
members,
|
||||||
null,
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
avatar == null ? null : new File(avatar));
|
avatar == null ? null : new File(avatar));
|
||||||
checkSendMessageResults(results.first(), results.second());
|
checkSendMessageResults(results.first(), results.second());
|
||||||
return groupId;
|
return groupId;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue