]> nmode's Git Repositories - signal-cli/blobdiff - lib/src/main/java/org/asamk/signal/manager/Manager.java
Extend updateProfile command to set family name
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / Manager.java
index 3c2d130d3d378e57ad0a1642cd7c86cd5e488b8d..05aec952aa02bfe33544883d376dc78eb93bf8a0 100644 (file)
@@ -118,6 +118,7 @@ import org.whispersystems.signalservice.api.profiles.ProfileAndCredential;
 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
 import org.whispersystems.signalservice.api.push.SignalServiceAddress;
 import org.whispersystems.signalservice.api.push.exceptions.MissingConfigurationException;
+import org.whispersystems.signalservice.api.push.exceptions.UnregisteredUserException;
 import org.whispersystems.signalservice.api.util.InvalidNumberException;
 import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
 import org.whispersystems.signalservice.api.util.SleepTimer;
@@ -316,11 +317,9 @@ public class Manager implements Closeable {
     public void checkAccountState() throws IOException {
         if (accountManager.getPreKeysCount() < ServiceConfig.PREKEY_MINIMUM_COUNT) {
             refreshPreKeys();
-            account.save();
         }
         if (account.getUuid() == null) {
             account.setUuid(accountManager.getOwnUuid());
-            account.save();
         }
         updateAccountAttributes();
     }
@@ -355,18 +354,22 @@ public class Manager implements Closeable {
     }
 
     /**
-     * @param name       if null, the previous name will be kept
+     * @param givenName  if null, the previous givenName will be kept
+     * @param familyName if null, the previous familyName will be kept
      * @param about      if null, the previous about text will be kept
      * @param aboutEmoji if null, the previous about emoji will be kept
      * @param avatar     if avatar is null the image from the local avatar store is used (if present),
-     *                   if it's Optional.absent(), the avatar will be removed
      */
-    public void setProfile(String name, String about, String aboutEmoji, Optional<File> avatar) throws IOException {
+    public void setProfile(
+            String givenName, final String familyName, String about, String aboutEmoji, Optional<File> avatar
+    ) throws IOException {
         var profile = getRecipientProfile(account.getSelfRecipientId());
         var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
-        if (name != null) {
-            builder.withGivenName(name);
-            builder.withFamilyName(null);
+        if (givenName != null) {
+            builder.withGivenName(givenName);
+        }
+        if (familyName != null) {
+            builder.withFamilyName(familyName);
         }
         if (about != null) {
             builder.withAbout(about);
@@ -382,8 +385,8 @@ public class Manager implements Closeable {
             accountManager.setVersionedProfile(account.getUuid(),
                     account.getProfileKey(),
                     newProfile.getInternalServiceName(),
-                    newProfile.getAbout(),
-                    newProfile.getAboutEmoji(),
+                    newProfile.getAbout() == null ? "" : newProfile.getAbout(),
+                    newProfile.getAboutEmoji() == null ? "" : newProfile.getAboutEmoji(),
                     streamDetails);
         }
 
@@ -408,16 +411,19 @@ public class Manager implements Closeable {
         // If this is the master device, other users can't send messages to this number anymore.
         // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
         accountManager.setGcmId(Optional.absent());
+
+        account.setRegistered(false);
+    }
+
+    public void deleteAccount() throws IOException {
         accountManager.deleteAccount();
 
         account.setRegistered(false);
-        account.save();
     }
 
     public List<DeviceInfo> getLinkedDevices() throws IOException {
         var devices = accountManager.getDevices();
         account.setMultiDevice(devices.size() > 1);
-        account.save();
         return devices;
     }
 
@@ -425,7 +431,6 @@ public class Manager implements Closeable {
         accountManager.removeDevice(deviceId);
         var devices = accountManager.getDevices();
         account.setMultiDevice(devices.size() > 1);
-        account.save();
     }
 
     public void addDeviceLink(URI linkUri) throws IOException, InvalidKeyException {
@@ -444,7 +449,6 @@ public class Manager implements Closeable {
                 Optional.of(account.getProfileKey().serialize()),
                 verificationCode);
         account.setMultiDevice(true);
-        account.save();
     }
 
     public void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException {
@@ -458,8 +462,7 @@ public class Manager implements Closeable {
 
             pinHelper.setRegistrationLockPin(pin.get(), masterKey);
 
-            account.setRegistrationLockPin(pin.get());
-            account.setPinMasterKey(masterKey);
+            account.setRegistrationLockPin(pin.get(), masterKey);
         } else {
             // Remove legacy registration lock
             accountManager.removeRegistrationLockV1();
@@ -467,10 +470,8 @@ public class Manager implements Closeable {
             // Remove KBS Pin
             pinHelper.removeRegistrationLockPin();
 
-            account.setRegistrationLockPin(null);
-            account.setPinMasterKey(null);
+            account.setRegistrationLockPin(null, null);
         }
-        account.save();
     }
 
     void refreshPreKeys() throws IOException {
@@ -531,12 +532,6 @@ public class Manager implements Closeable {
                 ServiceConfig.AUTOMATIC_NETWORK_RETRY);
     }
 
-    public Profile getRecipientProfile(
-            SignalServiceAddress address
-    ) {
-        return getRecipientProfile(resolveRecipient(address), false);
-    }
-
     public Profile getRecipientProfile(
             RecipientId recipientId
     ) {
@@ -604,10 +599,14 @@ public class Manager implements Closeable {
         final var profile = profileAndCredential.getProfile();
 
         try {
-            account.getIdentityKeyStore()
+            var newIdentity = account.getIdentityKeyStore()
                     .saveIdentity(recipientId,
                             new IdentityKey(Base64.getDecoder().decode(profile.getIdentityKey())),
                             new Date());
+
+            if (newIdentity) {
+                account.getSessionStore().archiveSessions(recipientId);
+            }
         } catch (InvalidKeyException ignored) {
             logger.warn("Got invalid identity key in profile for {}",
                     resolveSignalServiceAddress(recipientId).getLegacyIdentifier());
@@ -712,9 +711,10 @@ public class Manager implements Closeable {
     public Pair<Long, List<SendMessageResult>> sendGroupMessageReaction(
             String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, GroupId groupId
     ) throws IOException, InvalidNumberException, NotAGroupMemberException, GroupNotFoundException {
+        var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor);
         var reaction = new SignalServiceDataMessage.Reaction(emoji,
                 remove,
-                canonicalizeAndResolveSignalServiceAddress(targetAuthor),
+                resolveSignalServiceAddress(targetAuthorRecipientId),
                 targetSentTimestamp);
         final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction);
 
@@ -947,14 +947,15 @@ public class Manager implements Closeable {
         }
         g = (GroupInfoV1) group;
 
-        if (!g.isMember(resolveRecipient(recipient))) {
+        final var recipientId = resolveRecipient(recipient);
+        if (!g.isMember(recipientId)) {
             throw new NotAGroupMemberException(groupId, g.name);
         }
 
         var messageBuilder = getGroupUpdateMessageBuilder(g);
 
         // Send group message only to the recipient who requested it
-        return sendMessage(messageBuilder, Set.of(resolveRecipient(recipient)));
+        return sendMessage(messageBuilder, Set.of(recipientId));
     }
 
     private SignalServiceDataMessage.Builder getGroupUpdateMessageBuilder(GroupInfoV1 g) throws AttachmentInvalidException {
@@ -1064,9 +1065,10 @@ public class Manager implements Closeable {
     public Pair<Long, List<SendMessageResult>> sendMessageReaction(
             String emoji, boolean remove, String targetAuthor, long targetSentTimestamp, List<String> recipients
     ) throws IOException, InvalidNumberException {
+        var targetAuthorRecipientId = canonicalizeAndResolveRecipient(targetAuthor);
         var reaction = new SignalServiceDataMessage.Reaction(emoji,
                 remove,
-                canonicalizeAndResolveSignalServiceAddress(targetAuthor),
+                resolveSignalServiceAddress(targetAuthorRecipientId),
                 targetSentTimestamp);
         final var messageBuilder = SignalServiceDataMessage.newBuilder().withReaction(reaction);
         return sendMessage(messageBuilder, getSignalServiceAddresses(recipients));
@@ -1082,7 +1084,6 @@ public class Manager implements Closeable {
             for (var address : signalServiceAddresses) {
                 handleEndSession(address);
             }
-            account.save();
             throw e;
         }
     }
@@ -1092,15 +1093,22 @@ public class Manager implements Closeable {
         return contact == null || contact.getName() == null ? "" : contact.getName();
     }
 
-    public void setContactName(String number, String name) throws InvalidNumberException {
+    public void setContactName(String number, String name) throws InvalidNumberException, NotMasterDeviceException {
+        if (!account.isMasterDevice()) {
+            throw new NotMasterDeviceException();
+        }
         final var recipientId = canonicalizeAndResolveRecipient(number);
         var contact = account.getContactStore().getContact(recipientId);
         final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
         account.getContactStore().storeContact(recipientId, builder.withName(name).build());
-        account.save();
     }
 
-    public void setContactBlocked(String number, boolean blocked) throws InvalidNumberException {
+    public void setContactBlocked(
+            String number, boolean blocked
+    ) throws InvalidNumberException, NotMasterDeviceException {
+        if (!account.isMasterDevice()) {
+            throw new NotMasterDeviceException();
+        }
         setContactBlocked(canonicalizeAndResolveRecipient(number), blocked);
     }
 
@@ -1108,7 +1116,6 @@ public class Manager implements Closeable {
         var contact = account.getContactStore().getContact(recipientId);
         final var builder = contact == null ? Contact.newBuilder() : Contact.newBuilder(contact);
         account.getContactStore().storeContact(recipientId, builder.withBlocked(blocked).build());
-        account.save();
     }
 
     public void setGroupBlocked(final GroupId groupId, final boolean blocked) throws GroupNotFoundException {
@@ -1119,7 +1126,6 @@ public class Manager implements Closeable {
 
         group.setBlocked(blocked);
         account.getGroupStore().updateGroup(group);
-        account.save();
     }
 
     private void setExpirationTimer(RecipientId recipientId, int messageExpirationTimer) {
@@ -1146,7 +1152,6 @@ public class Manager implements Closeable {
         var recipientId = canonicalizeAndResolveRecipient(number);
         setExpirationTimer(recipientId, messageExpirationTimer);
         sendExpirationTimerUpdate(recipientId);
-        account.save();
     }
 
     /**
@@ -1179,7 +1184,6 @@ public class Manager implements Closeable {
 
         var sticker = new Sticker(StickerPackId.deserialize(Hex.fromStringCondensed(packId)), packKey);
         account.getStickerStore().updateSticker(sticker);
-        account.save();
 
         try {
             return new URI("https",
@@ -1193,7 +1197,15 @@ public class Manager implements Closeable {
         }
     }
 
-    void requestSyncGroups() throws IOException {
+    public void requestAllSyncData() throws IOException {
+        requestSyncGroups();
+        requestSyncContacts();
+        requestSyncBlocked();
+        requestSyncConfiguration();
+        requestSyncKeys();
+    }
+
+    private void requestSyncGroups() throws IOException {
         var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
                 .setType(SignalServiceProtos.SyncMessage.Request.Type.GROUPS)
                 .build();
@@ -1205,7 +1217,7 @@ public class Manager implements Closeable {
         }
     }
 
-    void requestSyncContacts() throws IOException {
+    private void requestSyncContacts() throws IOException {
         var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
                 .setType(SignalServiceProtos.SyncMessage.Request.Type.CONTACTS)
                 .build();
@@ -1217,7 +1229,7 @@ public class Manager implements Closeable {
         }
     }
 
-    void requestSyncBlocked() throws IOException {
+    private void requestSyncBlocked() throws IOException {
         var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
                 .setType(SignalServiceProtos.SyncMessage.Request.Type.BLOCKED)
                 .build();
@@ -1229,7 +1241,7 @@ public class Manager implements Closeable {
         }
     }
 
-    void requestSyncConfiguration() throws IOException {
+    private void requestSyncConfiguration() throws IOException {
         var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
                 .setType(SignalServiceProtos.SyncMessage.Request.Type.CONFIGURATION)
                 .build();
@@ -1241,7 +1253,7 @@ public class Manager implements Closeable {
         }
     }
 
-    void requestSyncKeys() throws IOException {
+    private void requestSyncKeys() throws IOException {
         var r = SignalServiceProtos.SyncMessage.Request.newBuilder()
                 .setType(SignalServiceProtos.SyncMessage.Request.Type.KEYS)
                 .build();
@@ -1277,7 +1289,7 @@ public class Manager implements Closeable {
         final var addressesMissingUuid = new HashSet<SignalServiceAddress>();
 
         for (var number : numbers) {
-            final var resolvedAddress = canonicalizeAndResolveSignalServiceAddress(number);
+            final var resolvedAddress = resolveSignalServiceAddress(canonicalizeAndResolveRecipient(number));
             if (resolvedAddress.getUuid().isPresent()) {
                 signalServiceAddresses.add(resolvedAddress);
             } else {
@@ -1311,10 +1323,20 @@ public class Manager implements Closeable {
         return signalServiceAddresses.stream().map(this::resolveRecipient).collect(Collectors.toSet());
     }
 
-    private Map<String, UUID> getRegisteredUsers(final Set<String> numbersMissingUuid) throws IOException {
+    private RecipientId refreshRegisteredUser(RecipientId recipientId) throws IOException {
+        final var address = resolveSignalServiceAddress(recipientId);
+        if (!address.getNumber().isPresent()) {
+            return recipientId;
+        }
+        final var number = address.getNumber().get();
+        final var uuidMap = getRegisteredUsers(Set.of(number));
+        return resolveRecipientTrusted(new SignalServiceAddress(uuidMap.getOrDefault(number, null), number));
+    }
+
+    private Map<String, UUID> getRegisteredUsers(final Set<String> numbers) throws IOException {
         try {
             return accountManager.getRegisteredUsers(ServiceConfig.getIasKeyStore(),
-                    numbersMissingUuid,
+                    numbers,
                     serviceEnvironmentConfig.getCdsMrenclave());
         } catch (Quote.InvalidQuoteFormatException | UnauthenticatedQuoteException | SignatureException | UnauthenticatedResponseException | InvalidKeyException e) {
             throw new IOException(e);
@@ -1346,10 +1368,12 @@ public class Manager implements Closeable {
 
                     for (var r : result) {
                         if (r.getIdentityFailure() != null) {
-                            account.getIdentityKeyStore().
-                                    saveIdentity(resolveRecipient(r.getAddress()),
-                                            r.getIdentityFailure().getIdentityKey(),
-                                            new Date());
+                            final var recipientId = resolveRecipient(r.getAddress());
+                            final var newIdentity = account.getIdentityKeyStore()
+                                    .saveIdentity(recipientId, r.getIdentityFailure().getIdentityKey(), new Date());
+                            if (newIdentity) {
+                                account.getSessionStore().archiveSessions(recipientId);
+                            }
                         }
                     }
 
@@ -1366,7 +1390,7 @@ public class Manager implements Closeable {
                     final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
                     messageBuilder.withExpiration(expirationTime);
                     message = messageBuilder.build();
-                    results.add(sendMessage(resolveSignalServiceAddress(recipientId), message));
+                    results.add(sendMessage(recipientId, message));
                 }
                 return new Pair<>(timestamp, results);
             }
@@ -1376,7 +1400,6 @@ public class Manager implements Closeable {
                     handleEndSession(recipient);
                 }
             }
-            account.save();
         }
     }
 
@@ -1387,27 +1410,24 @@ public class Manager implements Closeable {
         messageBuilder.withTimestamp(timestamp);
         getOrCreateMessagePipe();
         getOrCreateUnidentifiedMessagePipe();
-        try {
-            final var recipientId = account.getSelfRecipientId();
+        final var recipientId = account.getSelfRecipientId();
 
-            final var contact = account.getContactStore().getContact(recipientId);
-            final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
-            messageBuilder.withExpiration(expirationTime);
+        final var contact = account.getContactStore().getContact(recipientId);
+        final var expirationTime = contact != null ? contact.getMessageExpirationTime() : 0;
+        messageBuilder.withExpiration(expirationTime);
 
-            var message = messageBuilder.build();
-            final var result = sendSelfMessage(message);
-            return new Pair<>(timestamp, result);
-        } finally {
-            account.save();
-        }
+        var message = messageBuilder.build();
+        final var result = sendSelfMessage(message);
+        return new Pair<>(timestamp, result);
     }
 
     private SendMessageResult sendSelfMessage(SignalServiceDataMessage message) throws IOException {
         var messageSender = createMessageSender();
 
-        var recipient = account.getSelfAddress();
+        var recipientId = account.getSelfRecipientId();
 
-        final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(resolveRecipient(recipient));
+        final var unidentifiedAccess = unidentifiedAccessHelper.getAccessFor(recipientId);
+        var recipient = resolveSignalServiceAddress(recipientId);
         var transcript = new SentTranscriptMessage(Optional.of(recipient),
                 message.getTimestamp(),
                 message,
@@ -1429,14 +1449,20 @@ public class Manager implements Closeable {
     }
 
     private SendMessageResult sendMessage(
-            SignalServiceAddress address, SignalServiceDataMessage message
+            RecipientId recipientId, SignalServiceDataMessage message
     ) throws IOException {
         var messageSender = createMessageSender();
 
+        final var address = resolveSignalServiceAddress(recipientId);
         try {
-            return messageSender.sendMessage(address,
-                    unidentifiedAccessHelper.getAccessFor(resolveRecipient(address)),
-                    message);
+            try {
+                return messageSender.sendMessage(address, unidentifiedAccessHelper.getAccessFor(recipientId), message);
+            } catch (UnregisteredUserException e) {
+                final var newRecipientId = refreshRegisteredUser(recipientId);
+                return messageSender.sendMessage(resolveSignalServiceAddress(newRecipientId),
+                        unidentifiedAccessHelper.getAccessFor(newRecipientId),
+                        message);
+            }
         } catch (UntrustedIdentityException e) {
             return SendMessageResult.identityFailure(address, e.getIdentityKey());
         }
@@ -1658,11 +1684,11 @@ public class Manager implements Closeable {
 
     private void storeProfileKeysFromMembers(final DecryptedGroup group) {
         for (var member : group.getMembersList()) {
-            final var address = resolveRecipient(new SignalServiceAddress(UuidUtil.parseOrThrow(member.getUuid()
-                    .toByteArray()), null));
+            final var uuid = UuidUtil.parseOrThrow(member.getUuid().toByteArray());
+            final var recipientId = account.getRecipientStore().resolveRecipient(uuid);
             try {
                 account.getProfileStore()
-                        .storeProfileKey(address, new ProfileKey(member.getProfileKey().toByteArray()));
+                        .storeProfileKey(recipientId, new ProfileKey(member.getProfileKey().toByteArray()));
             } catch (InvalidInputException ignored) {
             }
         }
@@ -1699,8 +1725,8 @@ public class Manager implements Closeable {
                 content = decryptMessage(envelope);
             } catch (org.whispersystems.libsignal.UntrustedIdentityException e) {
                 if (!envelope.hasSource()) {
-                    final var recipientId = resolveRecipient(((org.whispersystems.libsignal.UntrustedIdentityException) e)
-                            .getName());
+                    final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) e).getName();
+                    final var recipientId = resolveRecipient(identifier);
                     try {
                         account.getMessageCache().replaceSender(cachedMessage, recipientId);
                     } catch (IOException ioException) {
@@ -1715,7 +1741,6 @@ public class Manager implements Closeable {
             }
             actions = handleMessage(envelope, content, ignoreAttachments);
         }
-        account.save();
         handler.handleMessage(envelope, content, null);
         cachedMessage.delete();
         return actions;
@@ -1763,7 +1788,6 @@ public class Manager implements Closeable {
                                 logger.warn("Message action failed.", e);
                             }
                         }
-                        account.save();
                         queuedActions.clear();
                         queuedActions = null;
                     }
@@ -1803,7 +1827,6 @@ public class Manager implements Closeable {
                     queuedActions.addAll(actions);
                 }
             }
-            account.save();
             if (isMessageBlocked(envelope, content)) {
                 logger.info("Ignoring a message from blocked user/group: {}", envelope.getTimestamp());
             } else if (notAGroupMember) {
@@ -1813,8 +1836,8 @@ public class Manager implements Closeable {
             }
             if (cachedMessage[0] != null) {
                 if (exception instanceof org.whispersystems.libsignal.UntrustedIdentityException) {
-                    final var recipientId = resolveRecipient(((org.whispersystems.libsignal.UntrustedIdentityException) exception)
-                            .getName());
+                    final var identifier = ((org.whispersystems.libsignal.UntrustedIdentityException) exception).getName();
+                    final var recipientId = resolveRecipient(identifier);
                     queuedActions.add(new RetrieveProfileAction(recipientId));
                     if (!envelope.hasSource()) {
                         try {
@@ -2529,14 +2552,6 @@ public class Manager implements Closeable {
                 theirIdentityKey);
     }
 
-    @Deprecated
-    public SignalServiceAddress canonicalizeAndResolveSignalServiceAddress(String identifier) throws InvalidNumberException {
-        var canonicalizedNumber = UuidUtil.isUuid(identifier)
-                ? identifier
-                : PhoneNumberFormatter.formatNumber(identifier, account.getUsername());
-        return resolveSignalServiceAddress(canonicalizedNumber);
-    }
-
     @Deprecated
     public SignalServiceAddress resolveSignalServiceAddress(String identifier) {
         var address = Utils.getSignalServiceAddressFromIdentifier(identifier);