Store profile phone number sharing mode and discoverable state
This commit is contained in:
		
							parent
							
								
									71de8e63cc
								
							
						
					
					
						commit
						7e0d4c9b89
					
				@ -3,5 +3,16 @@ package org.asamk.signal.manager.api;
 | 
			
		||||
public enum PhoneNumberSharingMode {
 | 
			
		||||
    EVERYBODY,
 | 
			
		||||
    CONTACTS,
 | 
			
		||||
    NOBODY,
 | 
			
		||||
    NOBODY;
 | 
			
		||||
 | 
			
		||||
    public static PhoneNumberSharingMode valueOfOrNull(String value) {
 | 
			
		||||
        if (value == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        try {
 | 
			
		||||
            return valueOf(value);
 | 
			
		||||
        } catch (IllegalArgumentException ignored) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,8 @@ public class Profile {
 | 
			
		||||
 | 
			
		||||
    private final Set<Capability> capabilities;
 | 
			
		||||
 | 
			
		||||
    private final PhoneNumberSharingMode phoneNumberSharingMode;
 | 
			
		||||
 | 
			
		||||
    public Profile(
 | 
			
		||||
            final long lastUpdateTimestamp,
 | 
			
		||||
            final String givenName,
 | 
			
		||||
@ -35,7 +37,8 @@ public class Profile {
 | 
			
		||||
            final String avatarUrlPath,
 | 
			
		||||
            final byte[] mobileCoinAddress,
 | 
			
		||||
            final UnidentifiedAccessMode unidentifiedAccessMode,
 | 
			
		||||
            final Set<Capability> capabilities
 | 
			
		||||
            final Set<Capability> capabilities,
 | 
			
		||||
            final PhoneNumberSharingMode phoneNumberSharingMode
 | 
			
		||||
    ) {
 | 
			
		||||
        this.lastUpdateTimestamp = lastUpdateTimestamp;
 | 
			
		||||
        this.givenName = givenName;
 | 
			
		||||
@ -46,6 +49,7 @@ public class Profile {
 | 
			
		||||
        this.mobileCoinAddress = mobileCoinAddress;
 | 
			
		||||
        this.unidentifiedAccessMode = unidentifiedAccessMode;
 | 
			
		||||
        this.capabilities = capabilities;
 | 
			
		||||
        this.phoneNumberSharingMode = phoneNumberSharingMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Profile(final Builder builder) {
 | 
			
		||||
@ -58,6 +62,7 @@ public class Profile {
 | 
			
		||||
        mobileCoinAddress = builder.mobileCoinAddress;
 | 
			
		||||
        unidentifiedAccessMode = builder.unidentifiedAccessMode;
 | 
			
		||||
        capabilities = builder.capabilities;
 | 
			
		||||
        phoneNumberSharingMode = builder.phoneNumberSharingMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Builder newBuilder() {
 | 
			
		||||
@ -136,6 +141,10 @@ public class Profile {
 | 
			
		||||
        return capabilities;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public PhoneNumberSharingMode getPhoneNumberSharingMode() {
 | 
			
		||||
        return phoneNumberSharingMode;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum UnidentifiedAccessMode {
 | 
			
		||||
        UNKNOWN,
 | 
			
		||||
        DISABLED,
 | 
			
		||||
@ -200,6 +209,7 @@ public class Profile {
 | 
			
		||||
        private byte[] mobileCoinAddress;
 | 
			
		||||
        private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
 | 
			
		||||
        private Set<Capability> capabilities = Collections.emptySet();
 | 
			
		||||
        private PhoneNumberSharingMode phoneNumberSharingMode;
 | 
			
		||||
        private long lastUpdateTimestamp = 0;
 | 
			
		||||
 | 
			
		||||
        private Builder() {
 | 
			
		||||
@ -240,6 +250,11 @@ public class Profile {
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder withPhoneNumberSharingMode(final PhoneNumberSharingMode val) {
 | 
			
		||||
            phoneNumberSharingMode = val;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Profile build() {
 | 
			
		||||
            return new Profile(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -20,13 +20,16 @@ public class Recipient {
 | 
			
		||||
 | 
			
		||||
    private final Profile profile;
 | 
			
		||||
 | 
			
		||||
    private final Boolean discoverable;
 | 
			
		||||
 | 
			
		||||
    public Recipient(
 | 
			
		||||
            final RecipientId recipientId,
 | 
			
		||||
            final RecipientAddress address,
 | 
			
		||||
            final Contact contact,
 | 
			
		||||
            final ProfileKey profileKey,
 | 
			
		||||
            final ExpiringProfileKeyCredential expiringProfileKeyCredential,
 | 
			
		||||
            final Profile profile
 | 
			
		||||
            final Profile profile,
 | 
			
		||||
            final Boolean discoverable
 | 
			
		||||
    ) {
 | 
			
		||||
        this.recipientId = recipientId;
 | 
			
		||||
        this.address = address;
 | 
			
		||||
@ -34,6 +37,7 @@ public class Recipient {
 | 
			
		||||
        this.profileKey = profileKey;
 | 
			
		||||
        this.expiringProfileKeyCredential = expiringProfileKeyCredential;
 | 
			
		||||
        this.profile = profile;
 | 
			
		||||
        this.discoverable = discoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Recipient(final Builder builder) {
 | 
			
		||||
@ -41,8 +45,9 @@ public class Recipient {
 | 
			
		||||
        address = builder.address;
 | 
			
		||||
        contact = builder.contact;
 | 
			
		||||
        profileKey = builder.profileKey;
 | 
			
		||||
        expiringProfileKeyCredential = builder.expiringProfileKeyCredential1;
 | 
			
		||||
        expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
 | 
			
		||||
        profile = builder.profile;
 | 
			
		||||
        discoverable = builder.discoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Builder newBuilder() {
 | 
			
		||||
@ -55,7 +60,7 @@ public class Recipient {
 | 
			
		||||
        builder.address = copy.getAddress();
 | 
			
		||||
        builder.contact = copy.getContact();
 | 
			
		||||
        builder.profileKey = copy.getProfileKey();
 | 
			
		||||
        builder.expiringProfileKeyCredential1 = copy.getExpiringProfileKeyCredential();
 | 
			
		||||
        builder.expiringProfileKeyCredential = copy.getExpiringProfileKeyCredential();
 | 
			
		||||
        builder.profile = copy.getProfile();
 | 
			
		||||
        return builder;
 | 
			
		||||
    }
 | 
			
		||||
@ -84,6 +89,10 @@ public class Recipient {
 | 
			
		||||
        return profile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Boolean getDiscoverable() {
 | 
			
		||||
        return discoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public boolean equals(final Object o) {
 | 
			
		||||
        if (this == o) return true;
 | 
			
		||||
@ -108,8 +117,9 @@ public class Recipient {
 | 
			
		||||
        private RecipientAddress address;
 | 
			
		||||
        private Contact contact;
 | 
			
		||||
        private ProfileKey profileKey;
 | 
			
		||||
        private ExpiringProfileKeyCredential expiringProfileKeyCredential1;
 | 
			
		||||
        private ExpiringProfileKeyCredential expiringProfileKeyCredential;
 | 
			
		||||
        private Profile profile;
 | 
			
		||||
        private Boolean discoverable;
 | 
			
		||||
 | 
			
		||||
        private Builder() {
 | 
			
		||||
        }
 | 
			
		||||
@ -135,7 +145,7 @@ public class Recipient {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
 | 
			
		||||
            expiringProfileKeyCredential1 = val;
 | 
			
		||||
            expiringProfileKeyCredential = val;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -144,6 +154,11 @@ public class Recipient {
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder withDiscoverable(final Boolean val) {
 | 
			
		||||
            discoverable = val;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Recipient build() {
 | 
			
		||||
            return new Recipient(this);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -363,6 +363,7 @@ public final class ProfileHelper {
 | 
			
		||||
 | 
			
		||||
            logger.trace("Storing profile");
 | 
			
		||||
            account.getProfileStore().storeProfile(recipientId, newProfile);
 | 
			
		||||
            account.getRecipientStore().markRegistered(recipientId, true);
 | 
			
		||||
 | 
			
		||||
            logger.trace("Done handling retrieved profile");
 | 
			
		||||
        }).doOnError(e -> {
 | 
			
		||||
@ -374,6 +375,10 @@ public final class ProfileHelper {
 | 
			
		||||
                    .withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
 | 
			
		||||
                    .withCapabilities(Set.of())
 | 
			
		||||
                    .build();
 | 
			
		||||
            if (e instanceof NotFoundException) {
 | 
			
		||||
                logger.debug("Marking recipient {} as unregistered after 404 profile fetch.", recipientId);
 | 
			
		||||
                account.getRecipientStore().markRegistered(recipientId, false);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            account.getProfileStore().storeProfile(recipientId, newProfile);
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
@ -187,7 +187,8 @@ public class RecipientHelper {
 | 
			
		||||
 | 
			
		||||
        final var unregisteredUsers = new HashSet<>(numbers);
 | 
			
		||||
        unregisteredUsers.removeAll(registeredUsers.keySet());
 | 
			
		||||
        account.getRecipientStore().markUnregistered(unregisteredUsers);
 | 
			
		||||
        account.getRecipientStore().markUndiscoverablePossiblyUnregistered(unregisteredUsers);
 | 
			
		||||
        account.getRecipientStore().markDiscoverable(registeredUsers.keySet());
 | 
			
		||||
 | 
			
		||||
        return registeredUsers;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1310,7 +1310,8 @@ public class ManagerImpl implements Manager {
 | 
			
		||||
                        s.getContact(),
 | 
			
		||||
                        s.getProfileKey(),
 | 
			
		||||
                        s.getExpiringProfileKeyCredential(),
 | 
			
		||||
                        s.getProfile()))
 | 
			
		||||
                        s.getProfile(),
 | 
			
		||||
                        s.getDiscoverable()))
 | 
			
		||||
                .toList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -33,7 +33,7 @@ import java.util.UUID;
 | 
			
		||||
public class AccountDatabase extends Database {
 | 
			
		||||
 | 
			
		||||
    private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
 | 
			
		||||
    private static final long DATABASE_VERSION = 25;
 | 
			
		||||
    private static final long DATABASE_VERSION = 26;
 | 
			
		||||
 | 
			
		||||
    private AccountDatabase(final HikariDataSource dataSource) {
 | 
			
		||||
        super(logger, DATABASE_VERSION, dataSource);
 | 
			
		||||
@ -591,6 +591,15 @@ public class AccountDatabase extends Database {
 | 
			
		||||
                                        """);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if (oldVersion < 26) {
 | 
			
		||||
            logger.debug("Updating database: Create discoverabel and profile_phone_number_sharing columns");
 | 
			
		||||
            try (final var statement = connection.createStatement()) {
 | 
			
		||||
                statement.executeUpdate("""
 | 
			
		||||
                                        ALTER TABLE recipient ADD discoverable INTEGER;
 | 
			
		||||
                                        ALTER TABLE recipient ADD profile_phone_number_sharing TEXT;
 | 
			
		||||
                                        """);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static void createUuidMappingTable(
 | 
			
		||||
 | 
			
		||||
@ -914,7 +914,8 @@ public class SignalAccount implements Closeable {
 | 
			
		||||
                                    : profile.getUnidentifiedAccess() != null
 | 
			
		||||
                                            ? Profile.UnidentifiedAccessMode.ENABLED
 | 
			
		||||
                                            : Profile.UnidentifiedAccessMode.DISABLED,
 | 
			
		||||
                            capabilities);
 | 
			
		||||
                            capabilities,
 | 
			
		||||
                            null);
 | 
			
		||||
                    getProfileStore().storeProfile(recipientId, newProfile);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -87,7 +87,8 @@ public class LegacyRecipientStore2 {
 | 
			
		||||
                            r.profile.capabilities.stream()
 | 
			
		||||
                                    .map(Profile.Capability::valueOfOrNull)
 | 
			
		||||
                                    .filter(Objects::nonNull)
 | 
			
		||||
                                    .collect(Collectors.toSet()));
 | 
			
		||||
                                    .collect(Collectors.toSet()),
 | 
			
		||||
                            null);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return new Recipient(recipientId,
 | 
			
		||||
@ -96,6 +97,7 @@ public class LegacyRecipientStore2 {
 | 
			
		||||
                        profileKey,
 | 
			
		||||
                        expiringProfileKeyCredential,
 | 
			
		||||
                        profile,
 | 
			
		||||
                        null,
 | 
			
		||||
                        null);
 | 
			
		||||
            }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -21,6 +21,8 @@ public class Recipient {
 | 
			
		||||
 | 
			
		||||
    private final Profile profile;
 | 
			
		||||
 | 
			
		||||
    private final Boolean discoverable;
 | 
			
		||||
 | 
			
		||||
    private final byte[] storageRecord;
 | 
			
		||||
 | 
			
		||||
    public Recipient(
 | 
			
		||||
@ -30,6 +32,7 @@ public class Recipient {
 | 
			
		||||
            final ProfileKey profileKey,
 | 
			
		||||
            final ExpiringProfileKeyCredential expiringProfileKeyCredential,
 | 
			
		||||
            final Profile profile,
 | 
			
		||||
            final Boolean discoverable,
 | 
			
		||||
            final byte[] storageRecord
 | 
			
		||||
    ) {
 | 
			
		||||
        this.recipientId = recipientId;
 | 
			
		||||
@ -38,6 +41,7 @@ public class Recipient {
 | 
			
		||||
        this.profileKey = profileKey;
 | 
			
		||||
        this.expiringProfileKeyCredential = expiringProfileKeyCredential;
 | 
			
		||||
        this.profile = profile;
 | 
			
		||||
        this.discoverable = discoverable;
 | 
			
		||||
        this.storageRecord = storageRecord;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -48,6 +52,7 @@ public class Recipient {
 | 
			
		||||
        profileKey = builder.profileKey;
 | 
			
		||||
        expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
 | 
			
		||||
        profile = builder.profile;
 | 
			
		||||
        discoverable = builder.discoverable;
 | 
			
		||||
        storageRecord = builder.storageRecord;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -91,6 +96,10 @@ public class Recipient {
 | 
			
		||||
        return profile;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Boolean getDiscoverable() {
 | 
			
		||||
        return discoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public byte[] getStorageRecord() {
 | 
			
		||||
        return storageRecord;
 | 
			
		||||
    }
 | 
			
		||||
@ -121,6 +130,7 @@ public class Recipient {
 | 
			
		||||
        private ProfileKey profileKey;
 | 
			
		||||
        private ExpiringProfileKeyCredential expiringProfileKeyCredential;
 | 
			
		||||
        private Profile profile;
 | 
			
		||||
        private Boolean discoverable;
 | 
			
		||||
        private byte[] storageRecord;
 | 
			
		||||
 | 
			
		||||
        private Builder() {
 | 
			
		||||
@ -156,6 +166,11 @@ public class Recipient {
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder withDiscoverable(final Boolean val) {
 | 
			
		||||
            discoverable = val;
 | 
			
		||||
            return this;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Builder withStorageRecord(final byte[] val) {
 | 
			
		||||
            storageRecord = val;
 | 
			
		||||
            return this;
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package org.asamk.signal.manager.storage.recipients;
 | 
			
		||||
 | 
			
		||||
import org.asamk.signal.manager.api.Contact;
 | 
			
		||||
import org.asamk.signal.manager.api.Pair;
 | 
			
		||||
import org.asamk.signal.manager.api.PhoneNumberSharingMode;
 | 
			
		||||
import org.asamk.signal.manager.api.Profile;
 | 
			
		||||
import org.asamk.signal.manager.api.UnregisteredRecipientException;
 | 
			
		||||
import org.asamk.signal.manager.storage.Database;
 | 
			
		||||
@ -64,6 +65,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                                      aci TEXT UNIQUE,
 | 
			
		||||
                                      pni TEXT UNIQUE,
 | 
			
		||||
                                      unregistered_timestamp INTEGER,
 | 
			
		||||
                                      discoverable INTEGER,
 | 
			
		||||
                                      profile_key BLOB,
 | 
			
		||||
                                      profile_key_credential BLOB,
 | 
			
		||||
                                      needs_pni_signature INTEGER NOT NULL DEFAULT FALSE,
 | 
			
		||||
@ -92,7 +94,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                                      profile_avatar_url_path TEXT,
 | 
			
		||||
                                      profile_mobile_coin_address BLOB,
 | 
			
		||||
                                      profile_unidentified_access_mode TEXT,
 | 
			
		||||
                                      profile_capabilities TEXT
 | 
			
		||||
                                      profile_capabilities TEXT,
 | 
			
		||||
                                      profile_phone_number_sharing TEXT
 | 
			
		||||
                                    ) STRICT;
 | 
			
		||||
                                    """);
 | 
			
		||||
        }
 | 
			
		||||
@ -354,7 +357,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                       r.number, r.aci, r.pni, r.username,
 | 
			
		||||
                       r.profile_key, r.profile_key_credential,
 | 
			
		||||
                       r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
 | 
			
		||||
                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities,
 | 
			
		||||
                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
 | 
			
		||||
                       r.discoverable,
 | 
			
		||||
                       r.storage_record
 | 
			
		||||
                FROM %s r
 | 
			
		||||
                WHERE r._id = ?
 | 
			
		||||
@ -373,7 +377,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                       r.number, r.aci, r.pni, r.username,
 | 
			
		||||
                       r.profile_key, r.profile_key_credential,
 | 
			
		||||
                       r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
 | 
			
		||||
                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities,
 | 
			
		||||
                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
 | 
			
		||||
                       r.discoverable,
 | 
			
		||||
                       r.storage_record
 | 
			
		||||
                FROM %s r
 | 
			
		||||
                WHERE r.storage_id = ?
 | 
			
		||||
@ -409,7 +414,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                       r.number, r.aci, r.pni, r.username,
 | 
			
		||||
                       r.profile_key, r.profile_key_credential,
 | 
			
		||||
                       r.given_name, r.family_name, r.nick_name, r.expiration_time, r.mute_until, r.hide_story, r.profile_sharing, r.color, r.blocked, r.archived, r.hidden, r.unregistered_timestamp,
 | 
			
		||||
                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities,
 | 
			
		||||
                       r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing,
 | 
			
		||||
                       r.discoverable,
 | 
			
		||||
                       r.storage_record
 | 
			
		||||
                FROM %s r
 | 
			
		||||
                WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s
 | 
			
		||||
@ -898,17 +904,53 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void markUnregistered(final Set<String> unregisteredUsers) {
 | 
			
		||||
        logger.debug("Marking {} numbers as unregistered", unregisteredUsers.size());
 | 
			
		||||
    public void markUndiscoverablePossiblyUnregistered(final Set<String> numbers) {
 | 
			
		||||
        logger.debug("Marking {} numbers as unregistered", numbers.size());
 | 
			
		||||
        try (final var connection = database.getConnection()) {
 | 
			
		||||
            connection.setAutoCommit(false);
 | 
			
		||||
            for (final var number : unregisteredUsers) {
 | 
			
		||||
                final var recipient = findByNumber(connection, number);
 | 
			
		||||
                if (recipient.isPresent()) {
 | 
			
		||||
                    final var recipientId = recipient.get().id();
 | 
			
		||||
            for (final var number : numbers) {
 | 
			
		||||
                final var recipientAddress = findByNumber(connection, number);
 | 
			
		||||
                if (recipientAddress.isPresent()) {
 | 
			
		||||
                    final var recipientId = recipientAddress.get().id();
 | 
			
		||||
                    markDiscoverable(connection, recipientId, false);
 | 
			
		||||
                    final var contact = getContact(connection, recipientId);
 | 
			
		||||
                    if (recipientAddress.get().address().aci().isEmpty() || contact.unregisteredTimestamp() != null) {
 | 
			
		||||
                        markUnregisteredAndSplitIfNecessary(connection, recipientId);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            connection.commit();
 | 
			
		||||
        } catch (SQLException e) {
 | 
			
		||||
            throw new RuntimeException("Failed update recipient store", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void markDiscoverable(final Set<String> numbers) {
 | 
			
		||||
        logger.debug("Marking {} numbers as discoverable", numbers.size());
 | 
			
		||||
        try (final var connection = database.getConnection()) {
 | 
			
		||||
            connection.setAutoCommit(false);
 | 
			
		||||
            for (final var number : numbers) {
 | 
			
		||||
                final var recipientAddress = findByNumber(connection, number);
 | 
			
		||||
                if (recipientAddress.isPresent()) {
 | 
			
		||||
                    final var recipientId = recipientAddress.get().id();
 | 
			
		||||
                    markDiscoverable(connection, recipientId, true);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            connection.commit();
 | 
			
		||||
        } catch (SQLException e) {
 | 
			
		||||
            throw new RuntimeException("Failed update recipient store", e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void markRegistered(final RecipientId recipientId, final boolean registered) {
 | 
			
		||||
        logger.debug("Marking {} as registered={}", recipientId, registered);
 | 
			
		||||
        try (final var connection = database.getConnection()) {
 | 
			
		||||
            connection.setAutoCommit(false);
 | 
			
		||||
            if (registered) {
 | 
			
		||||
                markRegistered(connection, recipientId);
 | 
			
		||||
            } else {
 | 
			
		||||
                markUnregistered(connection, recipientId);
 | 
			
		||||
            }
 | 
			
		||||
            connection.commit();
 | 
			
		||||
        } catch (SQLException e) {
 | 
			
		||||
            throw new RuntimeException("Failed update recipient store", e);
 | 
			
		||||
@ -927,6 +969,23 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void markDiscoverable(
 | 
			
		||||
            final Connection connection, final RecipientId recipientId, final boolean discoverable
 | 
			
		||||
    ) throws SQLException {
 | 
			
		||||
        final var sql = (
 | 
			
		||||
                """
 | 
			
		||||
                UPDATE %s
 | 
			
		||||
                SET discoverable = ?
 | 
			
		||||
                WHERE _id = ?
 | 
			
		||||
                """
 | 
			
		||||
        ).formatted(TABLE_RECIPIENT);
 | 
			
		||||
        try (final var statement = connection.prepareStatement(sql)) {
 | 
			
		||||
            statement.setBoolean(1, discoverable);
 | 
			
		||||
            statement.setLong(2, recipientId.id());
 | 
			
		||||
            statement.executeUpdate();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void markRegistered(
 | 
			
		||||
            final Connection connection, final RecipientId recipientId
 | 
			
		||||
    ) throws SQLException {
 | 
			
		||||
@ -949,8 +1008,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
        final var sql = (
 | 
			
		||||
                """
 | 
			
		||||
                UPDATE %s
 | 
			
		||||
                SET unregistered_timestamp = ?
 | 
			
		||||
                WHERE _id = ? AND unregistered_timestamp IS NULL
 | 
			
		||||
                SET unregistered_timestamp = ?, discoverable = FALSE
 | 
			
		||||
                WHERE _id = ?
 | 
			
		||||
                """
 | 
			
		||||
        ).formatted(TABLE_RECIPIENT);
 | 
			
		||||
        try (final var statement = connection.prepareStatement(sql)) {
 | 
			
		||||
@ -985,7 +1044,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
        final var sql = (
 | 
			
		||||
                """
 | 
			
		||||
                UPDATE %s
 | 
			
		||||
                SET profile_last_update_timestamp = ?, profile_given_name = ?, profile_family_name = ?, profile_about = ?, profile_about_emoji = ?, profile_avatar_url_path = ?, profile_mobile_coin_address = ?, profile_unidentified_access_mode = ?, profile_capabilities = ?
 | 
			
		||||
                SET profile_last_update_timestamp = ?, profile_given_name = ?, profile_family_name = ?, profile_about = ?, profile_about_emoji = ?, profile_avatar_url_path = ?, profile_mobile_coin_address = ?, profile_unidentified_access_mode = ?, profile_capabilities = ?, profile_phone_number_sharing = ?
 | 
			
		||||
                WHERE _id = ?
 | 
			
		||||
                """
 | 
			
		||||
        ).formatted(TABLE_RECIPIENT);
 | 
			
		||||
@ -1002,7 +1061,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                    profile == null
 | 
			
		||||
                            ? null
 | 
			
		||||
                            : profile.getCapabilities().stream().map(Enum::name).collect(Collectors.joining(",")));
 | 
			
		||||
            statement.setLong(10, recipientId.id());
 | 
			
		||||
            statement.setString(10,
 | 
			
		||||
                    profile == null || profile.getPhoneNumberSharingMode() == null
 | 
			
		||||
                            ? null
 | 
			
		||||
                            : profile.getPhoneNumberSharingMode().name());
 | 
			
		||||
            statement.setLong(11, recipientId.id());
 | 
			
		||||
            statement.executeUpdate();
 | 
			
		||||
        }
 | 
			
		||||
        rotateStorageId(connection, recipientId);
 | 
			
		||||
@ -1396,7 +1459,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
    public Profile getProfile(final Connection connection, final RecipientId recipientId) throws SQLException {
 | 
			
		||||
        final var sql = (
 | 
			
		||||
                """
 | 
			
		||||
                SELECT r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities
 | 
			
		||||
                SELECT r.profile_last_update_timestamp, r.profile_given_name, r.profile_family_name, r.profile_about, r.profile_about_emoji, r.profile_avatar_url_path, r.profile_mobile_coin_address, r.profile_unidentified_access_mode, r.profile_capabilities, r.profile_phone_number_sharing
 | 
			
		||||
                FROM %s r
 | 
			
		||||
                WHERE r._id = ? AND r.profile_capabilities IS NOT NULL
 | 
			
		||||
                """
 | 
			
		||||
@ -1431,6 +1494,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                getProfileKeyFromResultSet(resultSet),
 | 
			
		||||
                getExpiringProfileKeyCredentialFromResultSet(resultSet),
 | 
			
		||||
                getProfileFromResultSet(resultSet),
 | 
			
		||||
                getDiscoverableFromResultSet(resultSet),
 | 
			
		||||
                getStorageRecordFromResultSet(resultSet));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -1453,6 +1517,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                unregisteredTimestamp == 0 ? null : unregisteredTimestamp);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Boolean getDiscoverableFromResultSet(final ResultSet resultSet) throws SQLException {
 | 
			
		||||
        final var discoverable = resultSet.getBoolean("discoverable");
 | 
			
		||||
        if (resultSet.wasNull()) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
        return discoverable;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException {
 | 
			
		||||
        final var profileCapabilities = resultSet.getString("profile_capabilities");
 | 
			
		||||
        final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode");
 | 
			
		||||
@ -1471,7 +1543,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
 | 
			
		||||
                        : Arrays.stream(profileCapabilities.split(","))
 | 
			
		||||
                                .map(Profile.Capability::valueOfOrNull)
 | 
			
		||||
                                .filter(Objects::nonNull)
 | 
			
		||||
                                .collect(Collectors.toSet()));
 | 
			
		||||
                                .collect(Collectors.toSet()),
 | 
			
		||||
                PhoneNumberSharingMode.valueOfOrNull(resultSet.getString("profile_phone_number_sharing")));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ProfileKey getProfileKeyFromResultSet(ResultSet resultSet) throws SQLException {
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
package org.asamk.signal.manager.util;
 | 
			
		||||
 | 
			
		||||
import org.asamk.signal.manager.api.Pair;
 | 
			
		||||
import org.asamk.signal.manager.api.PhoneNumberSharingMode;
 | 
			
		||||
import org.asamk.signal.manager.api.Profile;
 | 
			
		||||
import org.signal.libsignal.protocol.IdentityKey;
 | 
			
		||||
import org.signal.libsignal.protocol.InvalidKeyException;
 | 
			
		||||
@ -16,6 +17,7 @@ import org.whispersystems.signalservice.internal.push.PaymentAddress;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.Base64;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.Optional;
 | 
			
		||||
 | 
			
		||||
public class ProfileUtils {
 | 
			
		||||
 | 
			
		||||
@ -33,11 +35,14 @@ public class ProfileUtils {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            var name = decrypt(encryptedProfile.getName(), profileCipher);
 | 
			
		||||
            var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher));
 | 
			
		||||
            var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher));
 | 
			
		||||
            var name = decryptString(encryptedProfile.getName(), profileCipher);
 | 
			
		||||
            var about = decryptString(encryptedProfile.getAbout(), profileCipher);
 | 
			
		||||
            var aboutEmoji = decryptString(encryptedProfile.getAboutEmoji(), profileCipher);
 | 
			
		||||
 | 
			
		||||
            final var nameParts = splitName(name);
 | 
			
		||||
            final var remotePhoneNumberSharing = decryptBoolean(encryptedProfile.getPhoneNumberSharing(),
 | 
			
		||||
                    profileCipher).map(v -> v ? PhoneNumberSharingMode.EVERYBODY : PhoneNumberSharingMode.NOBODY)
 | 
			
		||||
                    .orElse(null);
 | 
			
		||||
            return new Profile(System.currentTimeMillis(),
 | 
			
		||||
                    nameParts.first(),
 | 
			
		||||
                    nameParts.second(),
 | 
			
		||||
@ -50,7 +55,8 @@ public class ProfileUtils {
 | 
			
		||||
                                    profileCipher,
 | 
			
		||||
                                    identityKey.getPublicKey()),
 | 
			
		||||
                    getUnidentifiedAccessMode(encryptedProfile, profileCipher),
 | 
			
		||||
                    getCapabilities(encryptedProfile));
 | 
			
		||||
                    getCapabilities(encryptedProfile),
 | 
			
		||||
                    remotePhoneNumberSharing);
 | 
			
		||||
        } catch (InvalidCiphertextException e) {
 | 
			
		||||
            logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
 | 
			
		||||
            return null;
 | 
			
		||||
@ -83,18 +89,28 @@ public class ProfileUtils {
 | 
			
		||||
        return capabilities;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static String decrypt(
 | 
			
		||||
            final String encryptedName, final ProfileCipher profileCipher
 | 
			
		||||
    private static String decryptString(
 | 
			
		||||
            final String encrypted, final ProfileCipher profileCipher
 | 
			
		||||
    ) throws InvalidCiphertextException {
 | 
			
		||||
        try {
 | 
			
		||||
            return encryptedName == null
 | 
			
		||||
                    ? null
 | 
			
		||||
                    : new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
 | 
			
		||||
            return encrypted == null ? null : profileCipher.decryptString(Base64.getDecoder().decode(encrypted));
 | 
			
		||||
        } catch (IllegalArgumentException e) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static Optional<Boolean> decryptBoolean(
 | 
			
		||||
            final String encrypted, final ProfileCipher profileCipher
 | 
			
		||||
    ) throws InvalidCiphertextException {
 | 
			
		||||
        try {
 | 
			
		||||
            return encrypted == null
 | 
			
		||||
                    ? Optional.empty()
 | 
			
		||||
                    : profileCipher.decryptBoolean(Base64.getDecoder().decode(encrypted));
 | 
			
		||||
        } catch (IllegalArgumentException e) {
 | 
			
		||||
            return Optional.empty();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static byte[] decryptAndVerifyMobileCoinAddress(
 | 
			
		||||
            final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey
 | 
			
		||||
    ) throws InvalidCiphertextException {
 | 
			
		||||
@ -129,13 +145,4 @@ public class ProfileUtils {
 | 
			
		||||
            default -> new Pair<>(parts[0], parts[1]);
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static String trimZeros(String str) {
 | 
			
		||||
        if (str == null) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int pos = str.indexOf(0);
 | 
			
		||||
        return pos == -1 ? str : str.substring(0, pos);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user