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 {
|
public enum PhoneNumberSharingMode {
|
||||||
EVERYBODY,
|
EVERYBODY,
|
||||||
CONTACTS,
|
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 Set<Capability> capabilities;
|
||||||
|
|
||||||
|
private final PhoneNumberSharingMode phoneNumberSharingMode;
|
||||||
|
|
||||||
public Profile(
|
public Profile(
|
||||||
final long lastUpdateTimestamp,
|
final long lastUpdateTimestamp,
|
||||||
final String givenName,
|
final String givenName,
|
||||||
@ -35,7 +37,8 @@ public class Profile {
|
|||||||
final String avatarUrlPath,
|
final String avatarUrlPath,
|
||||||
final byte[] mobileCoinAddress,
|
final byte[] mobileCoinAddress,
|
||||||
final UnidentifiedAccessMode unidentifiedAccessMode,
|
final UnidentifiedAccessMode unidentifiedAccessMode,
|
||||||
final Set<Capability> capabilities
|
final Set<Capability> capabilities,
|
||||||
|
final PhoneNumberSharingMode phoneNumberSharingMode
|
||||||
) {
|
) {
|
||||||
this.lastUpdateTimestamp = lastUpdateTimestamp;
|
this.lastUpdateTimestamp = lastUpdateTimestamp;
|
||||||
this.givenName = givenName;
|
this.givenName = givenName;
|
||||||
@ -46,6 +49,7 @@ public class Profile {
|
|||||||
this.mobileCoinAddress = mobileCoinAddress;
|
this.mobileCoinAddress = mobileCoinAddress;
|
||||||
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
this.unidentifiedAccessMode = unidentifiedAccessMode;
|
||||||
this.capabilities = capabilities;
|
this.capabilities = capabilities;
|
||||||
|
this.phoneNumberSharingMode = phoneNumberSharingMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Profile(final Builder builder) {
|
private Profile(final Builder builder) {
|
||||||
@ -58,6 +62,7 @@ public class Profile {
|
|||||||
mobileCoinAddress = builder.mobileCoinAddress;
|
mobileCoinAddress = builder.mobileCoinAddress;
|
||||||
unidentifiedAccessMode = builder.unidentifiedAccessMode;
|
unidentifiedAccessMode = builder.unidentifiedAccessMode;
|
||||||
capabilities = builder.capabilities;
|
capabilities = builder.capabilities;
|
||||||
|
phoneNumberSharingMode = builder.phoneNumberSharingMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder newBuilder() {
|
public static Builder newBuilder() {
|
||||||
@ -136,6 +141,10 @@ public class Profile {
|
|||||||
return capabilities;
|
return capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PhoneNumberSharingMode getPhoneNumberSharingMode() {
|
||||||
|
return phoneNumberSharingMode;
|
||||||
|
}
|
||||||
|
|
||||||
public enum UnidentifiedAccessMode {
|
public enum UnidentifiedAccessMode {
|
||||||
UNKNOWN,
|
UNKNOWN,
|
||||||
DISABLED,
|
DISABLED,
|
||||||
@ -200,6 +209,7 @@ public class Profile {
|
|||||||
private byte[] mobileCoinAddress;
|
private byte[] mobileCoinAddress;
|
||||||
private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
|
private UnidentifiedAccessMode unidentifiedAccessMode = UnidentifiedAccessMode.UNKNOWN;
|
||||||
private Set<Capability> capabilities = Collections.emptySet();
|
private Set<Capability> capabilities = Collections.emptySet();
|
||||||
|
private PhoneNumberSharingMode phoneNumberSharingMode;
|
||||||
private long lastUpdateTimestamp = 0;
|
private long lastUpdateTimestamp = 0;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
@ -240,6 +250,11 @@ public class Profile {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withPhoneNumberSharingMode(final PhoneNumberSharingMode val) {
|
||||||
|
phoneNumberSharingMode = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Profile build() {
|
public Profile build() {
|
||||||
return new Profile(this);
|
return new Profile(this);
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,16 @@ public class Recipient {
|
|||||||
|
|
||||||
private final Profile profile;
|
private final Profile profile;
|
||||||
|
|
||||||
|
private final Boolean discoverable;
|
||||||
|
|
||||||
public Recipient(
|
public Recipient(
|
||||||
final RecipientId recipientId,
|
final RecipientId recipientId,
|
||||||
final RecipientAddress address,
|
final RecipientAddress address,
|
||||||
final Contact contact,
|
final Contact contact,
|
||||||
final ProfileKey profileKey,
|
final ProfileKey profileKey,
|
||||||
final ExpiringProfileKeyCredential expiringProfileKeyCredential,
|
final ExpiringProfileKeyCredential expiringProfileKeyCredential,
|
||||||
final Profile profile
|
final Profile profile,
|
||||||
|
final Boolean discoverable
|
||||||
) {
|
) {
|
||||||
this.recipientId = recipientId;
|
this.recipientId = recipientId;
|
||||||
this.address = address;
|
this.address = address;
|
||||||
@ -34,6 +37,7 @@ public class Recipient {
|
|||||||
this.profileKey = profileKey;
|
this.profileKey = profileKey;
|
||||||
this.expiringProfileKeyCredential = expiringProfileKeyCredential;
|
this.expiringProfileKeyCredential = expiringProfileKeyCredential;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
|
this.discoverable = discoverable;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Recipient(final Builder builder) {
|
private Recipient(final Builder builder) {
|
||||||
@ -41,8 +45,9 @@ public class Recipient {
|
|||||||
address = builder.address;
|
address = builder.address;
|
||||||
contact = builder.contact;
|
contact = builder.contact;
|
||||||
profileKey = builder.profileKey;
|
profileKey = builder.profileKey;
|
||||||
expiringProfileKeyCredential = builder.expiringProfileKeyCredential1;
|
expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
|
||||||
profile = builder.profile;
|
profile = builder.profile;
|
||||||
|
discoverable = builder.discoverable;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Builder newBuilder() {
|
public static Builder newBuilder() {
|
||||||
@ -55,7 +60,7 @@ public class Recipient {
|
|||||||
builder.address = copy.getAddress();
|
builder.address = copy.getAddress();
|
||||||
builder.contact = copy.getContact();
|
builder.contact = copy.getContact();
|
||||||
builder.profileKey = copy.getProfileKey();
|
builder.profileKey = copy.getProfileKey();
|
||||||
builder.expiringProfileKeyCredential1 = copy.getExpiringProfileKeyCredential();
|
builder.expiringProfileKeyCredential = copy.getExpiringProfileKeyCredential();
|
||||||
builder.profile = copy.getProfile();
|
builder.profile = copy.getProfile();
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
@ -84,6 +89,10 @@ public class Recipient {
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getDiscoverable() {
|
||||||
|
return discoverable;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(final Object o) {
|
public boolean equals(final Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
@ -108,8 +117,9 @@ public class Recipient {
|
|||||||
private RecipientAddress address;
|
private RecipientAddress address;
|
||||||
private Contact contact;
|
private Contact contact;
|
||||||
private ProfileKey profileKey;
|
private ProfileKey profileKey;
|
||||||
private ExpiringProfileKeyCredential expiringProfileKeyCredential1;
|
private ExpiringProfileKeyCredential expiringProfileKeyCredential;
|
||||||
private Profile profile;
|
private Profile profile;
|
||||||
|
private Boolean discoverable;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
}
|
}
|
||||||
@ -135,7 +145,7 @@ public class Recipient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
|
public Builder withExpiringProfileKeyCredential(final ExpiringProfileKeyCredential val) {
|
||||||
expiringProfileKeyCredential1 = val;
|
expiringProfileKeyCredential = val;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +154,11 @@ public class Recipient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withDiscoverable(final Boolean val) {
|
||||||
|
discoverable = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Recipient build() {
|
public Recipient build() {
|
||||||
return new Recipient(this);
|
return new Recipient(this);
|
||||||
}
|
}
|
||||||
|
@ -363,6 +363,7 @@ public final class ProfileHelper {
|
|||||||
|
|
||||||
logger.trace("Storing profile");
|
logger.trace("Storing profile");
|
||||||
account.getProfileStore().storeProfile(recipientId, newProfile);
|
account.getProfileStore().storeProfile(recipientId, newProfile);
|
||||||
|
account.getRecipientStore().markRegistered(recipientId, true);
|
||||||
|
|
||||||
logger.trace("Done handling retrieved profile");
|
logger.trace("Done handling retrieved profile");
|
||||||
}).doOnError(e -> {
|
}).doOnError(e -> {
|
||||||
@ -374,6 +375,10 @@ public final class ProfileHelper {
|
|||||||
.withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
|
.withUnidentifiedAccessMode(Profile.UnidentifiedAccessMode.UNKNOWN)
|
||||||
.withCapabilities(Set.of())
|
.withCapabilities(Set.of())
|
||||||
.build();
|
.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);
|
account.getProfileStore().storeProfile(recipientId, newProfile);
|
||||||
});
|
});
|
||||||
|
@ -187,7 +187,8 @@ public class RecipientHelper {
|
|||||||
|
|
||||||
final var unregisteredUsers = new HashSet<>(numbers);
|
final var unregisteredUsers = new HashSet<>(numbers);
|
||||||
unregisteredUsers.removeAll(registeredUsers.keySet());
|
unregisteredUsers.removeAll(registeredUsers.keySet());
|
||||||
account.getRecipientStore().markUnregistered(unregisteredUsers);
|
account.getRecipientStore().markUndiscoverablePossiblyUnregistered(unregisteredUsers);
|
||||||
|
account.getRecipientStore().markDiscoverable(registeredUsers.keySet());
|
||||||
|
|
||||||
return registeredUsers;
|
return registeredUsers;
|
||||||
}
|
}
|
||||||
|
@ -1310,7 +1310,8 @@ public class ManagerImpl implements Manager {
|
|||||||
s.getContact(),
|
s.getContact(),
|
||||||
s.getProfileKey(),
|
s.getProfileKey(),
|
||||||
s.getExpiringProfileKeyCredential(),
|
s.getExpiringProfileKeyCredential(),
|
||||||
s.getProfile()))
|
s.getProfile(),
|
||||||
|
s.getDiscoverable()))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ import java.util.UUID;
|
|||||||
public class AccountDatabase extends Database {
|
public class AccountDatabase extends Database {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(AccountDatabase.class);
|
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) {
|
private AccountDatabase(final HikariDataSource dataSource) {
|
||||||
super(logger, DATABASE_VERSION, 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(
|
private static void createUuidMappingTable(
|
||||||
|
@ -914,7 +914,8 @@ public class SignalAccount implements Closeable {
|
|||||||
: profile.getUnidentifiedAccess() != null
|
: profile.getUnidentifiedAccess() != null
|
||||||
? Profile.UnidentifiedAccessMode.ENABLED
|
? Profile.UnidentifiedAccessMode.ENABLED
|
||||||
: Profile.UnidentifiedAccessMode.DISABLED,
|
: Profile.UnidentifiedAccessMode.DISABLED,
|
||||||
capabilities);
|
capabilities,
|
||||||
|
null);
|
||||||
getProfileStore().storeProfile(recipientId, newProfile);
|
getProfileStore().storeProfile(recipientId, newProfile);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,7 +87,8 @@ public class LegacyRecipientStore2 {
|
|||||||
r.profile.capabilities.stream()
|
r.profile.capabilities.stream()
|
||||||
.map(Profile.Capability::valueOfOrNull)
|
.map(Profile.Capability::valueOfOrNull)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()),
|
||||||
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Recipient(recipientId,
|
return new Recipient(recipientId,
|
||||||
@ -96,6 +97,7 @@ public class LegacyRecipientStore2 {
|
|||||||
profileKey,
|
profileKey,
|
||||||
expiringProfileKeyCredential,
|
expiringProfileKeyCredential,
|
||||||
profile,
|
profile,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
}).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
|
}).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
|
||||||
|
|
||||||
|
@ -21,6 +21,8 @@ public class Recipient {
|
|||||||
|
|
||||||
private final Profile profile;
|
private final Profile profile;
|
||||||
|
|
||||||
|
private final Boolean discoverable;
|
||||||
|
|
||||||
private final byte[] storageRecord;
|
private final byte[] storageRecord;
|
||||||
|
|
||||||
public Recipient(
|
public Recipient(
|
||||||
@ -30,6 +32,7 @@ public class Recipient {
|
|||||||
final ProfileKey profileKey,
|
final ProfileKey profileKey,
|
||||||
final ExpiringProfileKeyCredential expiringProfileKeyCredential,
|
final ExpiringProfileKeyCredential expiringProfileKeyCredential,
|
||||||
final Profile profile,
|
final Profile profile,
|
||||||
|
final Boolean discoverable,
|
||||||
final byte[] storageRecord
|
final byte[] storageRecord
|
||||||
) {
|
) {
|
||||||
this.recipientId = recipientId;
|
this.recipientId = recipientId;
|
||||||
@ -38,6 +41,7 @@ public class Recipient {
|
|||||||
this.profileKey = profileKey;
|
this.profileKey = profileKey;
|
||||||
this.expiringProfileKeyCredential = expiringProfileKeyCredential;
|
this.expiringProfileKeyCredential = expiringProfileKeyCredential;
|
||||||
this.profile = profile;
|
this.profile = profile;
|
||||||
|
this.discoverable = discoverable;
|
||||||
this.storageRecord = storageRecord;
|
this.storageRecord = storageRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,6 +52,7 @@ public class Recipient {
|
|||||||
profileKey = builder.profileKey;
|
profileKey = builder.profileKey;
|
||||||
expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
|
expiringProfileKeyCredential = builder.expiringProfileKeyCredential;
|
||||||
profile = builder.profile;
|
profile = builder.profile;
|
||||||
|
discoverable = builder.discoverable;
|
||||||
storageRecord = builder.storageRecord;
|
storageRecord = builder.storageRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,6 +96,10 @@ public class Recipient {
|
|||||||
return profile;
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getDiscoverable() {
|
||||||
|
return discoverable;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getStorageRecord() {
|
public byte[] getStorageRecord() {
|
||||||
return storageRecord;
|
return storageRecord;
|
||||||
}
|
}
|
||||||
@ -121,6 +130,7 @@ public class Recipient {
|
|||||||
private ProfileKey profileKey;
|
private ProfileKey profileKey;
|
||||||
private ExpiringProfileKeyCredential expiringProfileKeyCredential;
|
private ExpiringProfileKeyCredential expiringProfileKeyCredential;
|
||||||
private Profile profile;
|
private Profile profile;
|
||||||
|
private Boolean discoverable;
|
||||||
private byte[] storageRecord;
|
private byte[] storageRecord;
|
||||||
|
|
||||||
private Builder() {
|
private Builder() {
|
||||||
@ -156,6 +166,11 @@ public class Recipient {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder withDiscoverable(final Boolean val) {
|
||||||
|
discoverable = val;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Builder withStorageRecord(final byte[] val) {
|
public Builder withStorageRecord(final byte[] val) {
|
||||||
storageRecord = val;
|
storageRecord = val;
|
||||||
return this;
|
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.Contact;
|
||||||
import org.asamk.signal.manager.api.Pair;
|
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.Profile;
|
||||||
import org.asamk.signal.manager.api.UnregisteredRecipientException;
|
import org.asamk.signal.manager.api.UnregisteredRecipientException;
|
||||||
import org.asamk.signal.manager.storage.Database;
|
import org.asamk.signal.manager.storage.Database;
|
||||||
@ -64,6 +65,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
aci TEXT UNIQUE,
|
aci TEXT UNIQUE,
|
||||||
pni TEXT UNIQUE,
|
pni TEXT UNIQUE,
|
||||||
unregistered_timestamp INTEGER,
|
unregistered_timestamp INTEGER,
|
||||||
|
discoverable INTEGER,
|
||||||
profile_key BLOB,
|
profile_key BLOB,
|
||||||
profile_key_credential BLOB,
|
profile_key_credential BLOB,
|
||||||
needs_pni_signature INTEGER NOT NULL DEFAULT FALSE,
|
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_avatar_url_path TEXT,
|
||||||
profile_mobile_coin_address BLOB,
|
profile_mobile_coin_address BLOB,
|
||||||
profile_unidentified_access_mode TEXT,
|
profile_unidentified_access_mode TEXT,
|
||||||
profile_capabilities TEXT
|
profile_capabilities TEXT,
|
||||||
|
profile_phone_number_sharing TEXT
|
||||||
) STRICT;
|
) STRICT;
|
||||||
""");
|
""");
|
||||||
}
|
}
|
||||||
@ -354,7 +357,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
r.number, r.aci, r.pni, r.username,
|
r.number, r.aci, r.pni, r.username,
|
||||||
r.profile_key, r.profile_key_credential,
|
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.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
|
r.storage_record
|
||||||
FROM %s r
|
FROM %s r
|
||||||
WHERE r._id = ?
|
WHERE r._id = ?
|
||||||
@ -373,7 +377,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
r.number, r.aci, r.pni, r.username,
|
r.number, r.aci, r.pni, r.username,
|
||||||
r.profile_key, r.profile_key_credential,
|
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.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
|
r.storage_record
|
||||||
FROM %s r
|
FROM %s r
|
||||||
WHERE r.storage_id = ?
|
WHERE r.storage_id = ?
|
||||||
@ -409,7 +414,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
r.number, r.aci, r.pni, r.username,
|
r.number, r.aci, r.pni, r.username,
|
||||||
r.profile_key, r.profile_key_credential,
|
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.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
|
r.storage_record
|
||||||
FROM %s r
|
FROM %s r
|
||||||
WHERE (r.number IS NOT NULL OR r.aci IS NOT NULL) AND %s
|
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) {
|
public void markUndiscoverablePossiblyUnregistered(final Set<String> numbers) {
|
||||||
logger.debug("Marking {} numbers as unregistered", unregisteredUsers.size());
|
logger.debug("Marking {} numbers as unregistered", numbers.size());
|
||||||
try (final var connection = database.getConnection()) {
|
try (final var connection = database.getConnection()) {
|
||||||
connection.setAutoCommit(false);
|
connection.setAutoCommit(false);
|
||||||
for (final var number : unregisteredUsers) {
|
for (final var number : numbers) {
|
||||||
final var recipient = findByNumber(connection, number);
|
final var recipientAddress = findByNumber(connection, number);
|
||||||
if (recipient.isPresent()) {
|
if (recipientAddress.isPresent()) {
|
||||||
final var recipientId = recipient.get().id();
|
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);
|
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();
|
connection.commit();
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException("Failed update recipient store", 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(
|
private void markRegistered(
|
||||||
final Connection connection, final RecipientId recipientId
|
final Connection connection, final RecipientId recipientId
|
||||||
) throws SQLException {
|
) throws SQLException {
|
||||||
@ -949,8 +1008,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
final var sql = (
|
final var sql = (
|
||||||
"""
|
"""
|
||||||
UPDATE %s
|
UPDATE %s
|
||||||
SET unregistered_timestamp = ?
|
SET unregistered_timestamp = ?, discoverable = FALSE
|
||||||
WHERE _id = ? AND unregistered_timestamp IS NULL
|
WHERE _id = ?
|
||||||
"""
|
"""
|
||||||
).formatted(TABLE_RECIPIENT);
|
).formatted(TABLE_RECIPIENT);
|
||||||
try (final var statement = connection.prepareStatement(sql)) {
|
try (final var statement = connection.prepareStatement(sql)) {
|
||||||
@ -985,7 +1044,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
final var sql = (
|
final var sql = (
|
||||||
"""
|
"""
|
||||||
UPDATE %s
|
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 = ?
|
WHERE _id = ?
|
||||||
"""
|
"""
|
||||||
).formatted(TABLE_RECIPIENT);
|
).formatted(TABLE_RECIPIENT);
|
||||||
@ -1002,7 +1061,11 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
profile == null
|
profile == null
|
||||||
? null
|
? null
|
||||||
: profile.getCapabilities().stream().map(Enum::name).collect(Collectors.joining(",")));
|
: 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();
|
statement.executeUpdate();
|
||||||
}
|
}
|
||||||
rotateStorageId(connection, recipientId);
|
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 {
|
public Profile getProfile(final Connection connection, final RecipientId recipientId) throws SQLException {
|
||||||
final var sql = (
|
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
|
FROM %s r
|
||||||
WHERE r._id = ? AND r.profile_capabilities IS NOT NULL
|
WHERE r._id = ? AND r.profile_capabilities IS NOT NULL
|
||||||
"""
|
"""
|
||||||
@ -1431,6 +1494,7 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
getProfileKeyFromResultSet(resultSet),
|
getProfileKeyFromResultSet(resultSet),
|
||||||
getExpiringProfileKeyCredentialFromResultSet(resultSet),
|
getExpiringProfileKeyCredentialFromResultSet(resultSet),
|
||||||
getProfileFromResultSet(resultSet),
|
getProfileFromResultSet(resultSet),
|
||||||
|
getDiscoverableFromResultSet(resultSet),
|
||||||
getStorageRecordFromResultSet(resultSet));
|
getStorageRecordFromResultSet(resultSet));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1453,6 +1517,14 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
unregisteredTimestamp == 0 ? null : unregisteredTimestamp);
|
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 {
|
private Profile getProfileFromResultSet(ResultSet resultSet) throws SQLException {
|
||||||
final var profileCapabilities = resultSet.getString("profile_capabilities");
|
final var profileCapabilities = resultSet.getString("profile_capabilities");
|
||||||
final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode");
|
final var profileUnidentifiedAccessMode = resultSet.getString("profile_unidentified_access_mode");
|
||||||
@ -1471,7 +1543,8 @@ public class RecipientStore implements RecipientIdCreator, RecipientResolver, Re
|
|||||||
: Arrays.stream(profileCapabilities.split(","))
|
: Arrays.stream(profileCapabilities.split(","))
|
||||||
.map(Profile.Capability::valueOfOrNull)
|
.map(Profile.Capability::valueOfOrNull)
|
||||||
.filter(Objects::nonNull)
|
.filter(Objects::nonNull)
|
||||||
.collect(Collectors.toSet()));
|
.collect(Collectors.toSet()),
|
||||||
|
PhoneNumberSharingMode.valueOfOrNull(resultSet.getString("profile_phone_number_sharing")));
|
||||||
}
|
}
|
||||||
|
|
||||||
private ProfileKey getProfileKeyFromResultSet(ResultSet resultSet) throws SQLException {
|
private ProfileKey getProfileKeyFromResultSet(ResultSet resultSet) throws SQLException {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package org.asamk.signal.manager.util;
|
package org.asamk.signal.manager.util;
|
||||||
|
|
||||||
import org.asamk.signal.manager.api.Pair;
|
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.Profile;
|
||||||
import org.signal.libsignal.protocol.IdentityKey;
|
import org.signal.libsignal.protocol.IdentityKey;
|
||||||
import org.signal.libsignal.protocol.InvalidKeyException;
|
import org.signal.libsignal.protocol.InvalidKeyException;
|
||||||
@ -16,6 +17,7 @@ import org.whispersystems.signalservice.internal.push.PaymentAddress;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class ProfileUtils {
|
public class ProfileUtils {
|
||||||
|
|
||||||
@ -33,11 +35,14 @@ public class ProfileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var name = decrypt(encryptedProfile.getName(), profileCipher);
|
var name = decryptString(encryptedProfile.getName(), profileCipher);
|
||||||
var about = trimZeros(decrypt(encryptedProfile.getAbout(), profileCipher));
|
var about = decryptString(encryptedProfile.getAbout(), profileCipher);
|
||||||
var aboutEmoji = trimZeros(decrypt(encryptedProfile.getAboutEmoji(), profileCipher));
|
var aboutEmoji = decryptString(encryptedProfile.getAboutEmoji(), profileCipher);
|
||||||
|
|
||||||
final var nameParts = splitName(name);
|
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(),
|
return new Profile(System.currentTimeMillis(),
|
||||||
nameParts.first(),
|
nameParts.first(),
|
||||||
nameParts.second(),
|
nameParts.second(),
|
||||||
@ -50,7 +55,8 @@ public class ProfileUtils {
|
|||||||
profileCipher,
|
profileCipher,
|
||||||
identityKey.getPublicKey()),
|
identityKey.getPublicKey()),
|
||||||
getUnidentifiedAccessMode(encryptedProfile, profileCipher),
|
getUnidentifiedAccessMode(encryptedProfile, profileCipher),
|
||||||
getCapabilities(encryptedProfile));
|
getCapabilities(encryptedProfile),
|
||||||
|
remotePhoneNumberSharing);
|
||||||
} catch (InvalidCiphertextException e) {
|
} catch (InvalidCiphertextException e) {
|
||||||
logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
|
logger.debug("Failed to decrypt profile for {}", encryptedProfile.getServiceId(), e);
|
||||||
return null;
|
return null;
|
||||||
@ -83,18 +89,28 @@ public class ProfileUtils {
|
|||||||
return capabilities;
|
return capabilities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String decrypt(
|
private static String decryptString(
|
||||||
final String encryptedName, final ProfileCipher profileCipher
|
final String encrypted, final ProfileCipher profileCipher
|
||||||
) throws InvalidCiphertextException {
|
) throws InvalidCiphertextException {
|
||||||
try {
|
try {
|
||||||
return encryptedName == null
|
return encrypted == null ? null : profileCipher.decryptString(Base64.getDecoder().decode(encrypted));
|
||||||
? null
|
|
||||||
: new String(profileCipher.decrypt(Base64.getDecoder().decode(encryptedName)));
|
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
return null;
|
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(
|
private static byte[] decryptAndVerifyMobileCoinAddress(
|
||||||
final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey
|
final byte[] encryptedPaymentAddress, final ProfileCipher profileCipher, final ECPublicKey publicKey
|
||||||
) throws InvalidCiphertextException {
|
) throws InvalidCiphertextException {
|
||||||
@ -129,13 +145,4 @@ public class ProfileUtils {
|
|||||||
default -> new Pair<>(parts[0], parts[1]);
|
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