]> nmode's Git Repositories - signal-cli/commitdiff
Implement add/remove admin privileges
authorAsamK <asamk@gmx.de>
Sat, 15 May 2021 09:08:01 +0000 (11:08 +0200)
committerAsamK <asamk@gmx.de>
Sat, 15 May 2021 15:04:22 +0000 (17:04 +0200)
lib/src/main/java/org/asamk/signal/manager/Manager.java
lib/src/main/java/org/asamk/signal/manager/helper/GroupV2Helper.java
lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfo.java
lib/src/main/java/org/asamk/signal/manager/storage/groups/GroupInfoV2.java
src/main/java/org/asamk/signal/commands/ListGroupsCommand.java
src/main/java/org/asamk/signal/commands/UpdateGroupCommand.java
src/main/java/org/asamk/signal/dbus/DbusSignalImpl.java

index cd0ce561a4b9ce08edc0fb91fa4281ca35f87fc5..b003279a8219685441044039045660dfc9d23f75 100644 (file)
@@ -831,6 +831,8 @@ public class Manager implements Closeable {
             String description,
             List<String> members,
             List<String> removeMembers,
+            List<String> admins,
+            List<String> removeAdmins,
             File avatarFile
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, InvalidNumberException, NotAGroupMemberException {
         return updateGroup(groupId,
@@ -838,21 +840,32 @@ public class Manager implements Closeable {
                 description,
                 members == null ? null : getSignalServiceAddresses(members),
                 removeMembers == null ? null : getSignalServiceAddresses(removeMembers),
+                admins == null ? null : getSignalServiceAddresses(admins),
+                removeAdmins == null ? null : getSignalServiceAddresses(removeAdmins),
                 avatarFile);
     }
 
     private Pair<Long, List<SendMessageResult>> updateGroup(
-            GroupId groupId,
-            String name,
-            String description,
-            Set<RecipientId> members,
+            final GroupId groupId,
+            final String name,
+            final String description,
+            final Set<RecipientId> members,
             final Set<RecipientId> removeMembers,
-            File avatarFile
+            final Set<RecipientId> admins,
+            final Set<RecipientId> removeAdmins,
+            final File avatarFile
     ) throws IOException, GroupNotFoundException, AttachmentInvalidException, NotAGroupMemberException {
         var group = getGroupForUpdating(groupId);
 
         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);
@@ -913,6 +926,8 @@ public class Manager implements Closeable {
             final String description,
             final Set<RecipientId> members,
             final Set<RecipientId> removeMembers,
+            final Set<RecipientId> admins,
+            final Set<RecipientId> removeAdmins,
             final File avatarFile
     ) throws IOException {
         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) {
             var groupGroupChangePair = groupV2Helper.updateGroup(group, name, description, avatarFile);
             if (avatarFile != null) {
index 94df2a4102fe9dc2dc292530c01abe43aa1f8d8f..6dab6dab01578ca6c3375fa0a5ba9458ba48f01a 100644 (file)
@@ -227,8 +227,7 @@ public class GroupV2Helper {
     public Pair<DecryptedGroup, GroupChange> addMembers(
             GroupInfoV2 groupInfoV2, Set<RecipientId> newMembers
     ) throws IOException {
-        final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
-        var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
+        GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
 
         if (!areMembersValid(newMembers)) {
             throw new IOException("Failed to update group");
@@ -318,8 +317,7 @@ public class GroupV2Helper {
     }
 
     public Pair<DecryptedGroup, GroupChange> acceptInvite(GroupInfoV2 groupInfoV2) throws IOException {
-        final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
-        final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
+        final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
 
         final var selfRecipientId = this.selfRecipientIdProvider.getSelfRecipientId();
         final var profileKeyCredential = profileKeyCredentialProvider.getProfileKeyCredential(selfRecipientId);
@@ -337,11 +335,25 @@ public class GroupV2Helper {
         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(
             GroupInfoV2 groupInfoV2, Set<DecryptedPendingMember> pendingMembers
     ) throws IOException {
-        final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
-        final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
+        final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
         final var uuidCipherTexts = pendingMembers.stream().map(member -> {
             try {
                 return new UuidCiphertext(member.getUuidCipherText().toByteArray());
@@ -355,8 +367,7 @@ public class GroupV2Helper {
     private Pair<DecryptedGroup, GroupChange> ejectMembers(
             GroupInfoV2 groupInfoV2, Set<UUID> uuids
     ) throws IOException {
-        final var groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupInfoV2.getMasterKey());
-        final var groupOperations = groupsV2Operations.forGroup(groupSecretParams);
+        final GroupsV2Operations.GroupOperations groupOperations = getGroupOperations(groupInfoV2);
         return commitChange(groupInfoV2, groupOperations.createRemoveMembersChange(uuids));
     }
 
index e2aaff4cf1a1715a761ad188d71afafc74f8241a..211e0f96b7b2d2afea44994c82674c682ef29921 100644 (file)
@@ -30,6 +30,10 @@ public abstract class GroupInfo {
         return Set.of();
     }
 
+    public Set<RecipientId> getAdminMembers() {
+        return Set.of();
+    }
+
     public abstract boolean isBlocked();
 
     public abstract void setBlocked(boolean blocked);
index 859c72096e7440fa46b45d1fff7c46094495ae67..308220cbd6a9d5bb4517cf09c1adfbb87befe386 100644 (file)
@@ -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.RecipientResolver;
 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.zkgroup.groups.GroupMasterKey;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
@@ -116,6 +117,19 @@ public class GroupInfoV2 extends GroupInfo {
                 .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
     public boolean isBlocked() {
         return blocked;
index 4886eac4935677c7837ac743bb7b63a3462e3865..aab91077676feead0dd5da430cf29591f4e573f9 100644 (file)
@@ -38,7 +38,7 @@ public class ListGroupsCommand implements LocalCommand {
             final var groupInviteLink = group.getGroupInviteLink();
 
             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.getTitle(),
                     group.getDescription(),
@@ -47,6 +47,7 @@ public class ListGroupsCommand implements LocalCommand {
                     resolveMembers(m, group.getMembers()),
                     resolveMembers(m, group.getPendingMembers()),
                     resolveMembers(m, group.getRequestingMembers()),
+                    resolveMembers(m, group.getAdminMembers()),
                     groupInviteLink == null ? '-' : groupInviteLink.getUrl());
         } else {
             writer.println("Id: {} Name: {}  Active: {} Blocked: {}",
@@ -88,6 +89,7 @@ public class ListGroupsCommand implements LocalCommand {
                         resolveMembers(m, group.getMembers()),
                         resolveMembers(m, group.getPendingMembers()),
                         resolveMembers(m, group.getRequestingMembers()),
+                        resolveMembers(m, group.getAdminMembers()),
                         groupInviteLink == null ? null : groupInviteLink.getUrl()));
             }
 
@@ -112,6 +114,7 @@ public class ListGroupsCommand implements LocalCommand {
         public Set<String> members;
         public Set<String> pendingMembers;
         public Set<String> requestingMembers;
+        public Set<String> admins;
         public String groupInviteLink;
 
         public JsonGroup(
@@ -123,6 +126,7 @@ public class ListGroupsCommand implements LocalCommand {
                 Set<String> members,
                 Set<String> pendingMembers,
                 Set<String> requestingMembers,
+                Set<String> admins,
                 String groupInviteLink
         ) {
             this.id = id;
@@ -134,6 +138,7 @@ public class ListGroupsCommand implements LocalCommand {
             this.members = members;
             this.pendingMembers = pendingMembers;
             this.requestingMembers = requestingMembers;
+            this.admins = admins;
             this.groupInviteLink = groupInviteLink;
         }
     }
index 513ec2e4c7b0fed88f16211eb9b9534119cb395a..873074467e9956462e6576cfcf6877112369b859 100644 (file)
@@ -41,6 +41,11 @@ public class UpdateGroupCommand implements DbusCommand, LocalCommand {
         subparser.addArgument("-r", "--remove-member")
                 .nargs("*")
                 .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
@@ -64,6 +69,10 @@ public class UpdateGroupCommand implements DbusCommand, LocalCommand {
 
         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");
 
         try {
@@ -80,6 +89,8 @@ public class UpdateGroupCommand implements DbusCommand, LocalCommand {
                         groupDescription,
                         groupMembers,
                         groupRemoveMembers,
+                        groupAdmins,
+                        groupRemoveAdmins,
                         groupAvatar == null ? null : new File(groupAvatar));
                 ErrorUtils.handleTimestampAndSendMessageResults(writer, results.first(), results.second());
             }
index 6606e966a14ac78fc8476cdfde29f0d421cf07ba..9b83ace51b4b33b535d874ac917bade4b1195c0f 100644 (file)
@@ -345,6 +345,8 @@ public class DbusSignalImpl implements Signal {
                         null,
                         members,
                         null,
+                        null,
+                        null,
                         avatar == null ? null : new File(avatar));
                 checkSendMessageResults(results.first(), results.second());
                 return groupId;