Refactor Manager interface

This commit is contained in:
AsamK 2021-09-18 10:19:56 +02:00
parent b91c162159
commit d72b838560
33 changed files with 416 additions and 169 deletions

View file

@ -1,6 +1,8 @@
package org.asamk.signal.manager;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Group;
import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
@ -17,12 +19,10 @@ import org.asamk.signal.manager.groups.GroupSendingNotAllowedException;
import org.asamk.signal.manager.groups.LastGroupAdminException;
import org.asamk.signal.manager.groups.NotAGroupMemberException;
import org.asamk.signal.manager.storage.SignalAccount;
import org.asamk.signal.manager.storage.groups.GroupInfo;
import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.identities.TrustNewIdentity;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.util.Pair;
@ -51,7 +51,7 @@ import java.util.stream.Collectors;
public interface Manager extends Closeable {
static Manager init(
String username,
String number,
File settingsPath,
ServiceEnvironment serviceEnvironment,
String userAgent,
@ -59,11 +59,11 @@ public interface Manager extends Closeable {
) throws IOException, NotRegisteredException {
var pathConfig = PathConfig.createDefault(settingsPath);
if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
throw new NotRegisteredException();
}
var account = SignalAccount.load(pathConfig.getDataPath(), username, true, trustNewIdentity);
var account = SignalAccount.load(pathConfig.getDataPath(), number, true, trustNewIdentity);
if (!account.isRegistered()) {
throw new NotRegisteredException();
@ -74,7 +74,7 @@ public interface Manager extends Closeable {
return new ManagerImpl(account, pathConfig, serviceEnvironmentConfig, userAgent);
}
static List<String> getAllLocalUsernames(File settingsPath) {
static List<String> getAllLocalNumbers(File settingsPath) {
var pathConfig = PathConfig.createDefault(settingsPath);
final var dataPath = pathConfig.getDataPath();
final var files = dataPath.listFiles();
@ -90,11 +90,7 @@ public interface Manager extends Closeable {
.collect(Collectors.toList());
}
String getUsername();
RecipientId getSelfRecipientId();
int getDeviceId();
String getSelfNumber();
void checkAccountState() throws IOException;
@ -120,9 +116,9 @@ public interface Manager extends Closeable {
void setRegistrationLockPin(Optional<String> pin) throws IOException, UnauthenticatedResponseException;
Profile getRecipientProfile(RecipientId recipientId);
Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException;
List<GroupInfo> getGroups();
List<Group> getGroups();
SendGroupMessageResults quitGroup(
GroupId groupId, Set<RecipientIdentifier.Single> groupAdmins
@ -221,15 +217,15 @@ public interface Manager extends Closeable {
void sendContacts() throws IOException;
List<Pair<RecipientId, Contact>> getContacts();
List<Pair<RecipientAddress, Contact>> getContacts();
String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier);
String getContactOrProfileName(RecipientIdentifier.Single recipient);
GroupInfo getGroup(GroupId groupId);
Group getGroup(GroupId groupId);
List<IdentityInfo> getIdentities();
List<Identity> getIdentities();
List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient);
List<Identity> getIdentities(RecipientIdentifier.Single recipient);
boolean trustIdentityVerified(RecipientIdentifier.Single recipient, byte[] fingerprint);
@ -241,14 +237,8 @@ public interface Manager extends Closeable {
String computeSafetyNumber(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey);
SignalServiceAddress resolveSignalServiceAddress(SignalServiceAddress address);
SignalServiceAddress resolveSignalServiceAddress(UUID uuid);
SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId);
@Override
void close() throws IOException;

View file

@ -18,6 +18,8 @@ package org.asamk.signal.manager;
import org.asamk.signal.manager.actions.HandleAction;
import org.asamk.signal.manager.api.Device;
import org.asamk.signal.manager.api.Group;
import org.asamk.signal.manager.api.Identity;
import org.asamk.signal.manager.api.Message;
import org.asamk.signal.manager.api.RecipientIdentifier;
import org.asamk.signal.manager.api.SendGroupMessageResults;
@ -52,6 +54,7 @@ import org.asamk.signal.manager.storage.identities.IdentityInfo;
import org.asamk.signal.manager.storage.messageCache.CachedMessage;
import org.asamk.signal.manager.storage.recipients.Contact;
import org.asamk.signal.manager.storage.recipients.Profile;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.asamk.signal.manager.storage.recipients.RecipientId;
import org.asamk.signal.manager.storage.stickers.Sticker;
import org.asamk.signal.manager.storage.stickers.StickerPackId;
@ -196,7 +199,7 @@ public class ManagerImpl implements Manager {
this::resolveSignalServiceAddress,
account.getRecipientStore(),
this::handleIdentityFailure,
this::getGroup,
this::getGroupInfo,
this::refreshRegisteredUser);
this.groupHelper = new GroupHelper(account,
dependencies,
@ -240,20 +243,10 @@ public class ManagerImpl implements Manager {
}
@Override
public String getUsername() {
public String getSelfNumber() {
return account.getUsername();
}
@Override
public RecipientId getSelfRecipientId() {
return account.getSelfRecipientId();
}
@Override
public int getDeviceId() {
return account.getDeviceId();
}
@Override
public void checkAccountState() throws IOException {
if (account.getLastReceiveTimestamp() == 0) {
@ -385,7 +378,11 @@ public class ManagerImpl implements Manager {
logger.debug("Failed to decrypt device name, maybe plain text?", e);
}
}
return new Device(d.getId(), deviceName, d.getCreated(), d.getLastSeen());
return new Device(d.getId(),
deviceName,
d.getCreated(),
d.getLastSeen(),
d.getId() == account.getDeviceId());
}).collect(Collectors.toList());
}
@ -442,13 +439,48 @@ public class ManagerImpl implements Manager {
}
@Override
public Profile getRecipientProfile(RecipientId recipientId) {
public Profile getRecipientProfile(RecipientIdentifier.Single recipient) throws UnregisteredUserException {
return profileHelper.getRecipientProfile(resolveRecipient(recipient));
}
private Profile getRecipientProfile(RecipientId recipientId) {
return profileHelper.getRecipientProfile(recipientId);
}
@Override
public List<GroupInfo> getGroups() {
return account.getGroupStore().getGroups();
public List<Group> getGroups() {
return account.getGroupStore().getGroups().stream().map(this::toGroup).collect(Collectors.toList());
}
private Group toGroup(final GroupInfo groupInfo) {
if (groupInfo == null) {
return null;
}
return new Group(groupInfo.getGroupId(),
groupInfo.getTitle(),
groupInfo.getDescription(),
groupInfo.getGroupInviteLink(),
groupInfo.getMembers()
.stream()
.map(account.getRecipientStore()::resolveRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getPendingMembers()
.stream()
.map(account.getRecipientStore()::resolveRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getRequestingMembers()
.stream()
.map(account.getRecipientStore()::resolveRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.getAdminMembers()
.stream()
.map(account.getRecipientStore()::resolveRecipientAddress)
.collect(Collectors.toSet()),
groupInfo.isBlocked(),
groupInfo.getMessageExpirationTime(),
groupInfo.isAnnouncementGroup(),
groupInfo.isMember(account.getSelfRecipientId()));
}
@Override
@ -973,15 +1005,19 @@ public class ManagerImpl implements Manager {
}
@Override
public List<Pair<RecipientId, Contact>> getContacts() {
return account.getContactStore().getContacts();
public List<Pair<RecipientAddress, Contact>> getContacts() {
return account.getContactStore()
.getContacts()
.stream()
.map(p -> new Pair<>(account.getRecipientStore().resolveRecipientAddress(p.first()), p.second()))
.collect(Collectors.toList());
}
@Override
public String getContactOrProfileName(RecipientIdentifier.Single recipientIdentifier) {
public String getContactOrProfileName(RecipientIdentifier.Single recipient) {
final RecipientId recipientId;
try {
recipientId = resolveRecipient(recipientIdentifier);
recipientId = resolveRecipient(recipient);
} catch (UnregisteredUserException e) {
return null;
}
@ -1000,24 +1036,46 @@ public class ManagerImpl implements Manager {
}
@Override
public GroupInfo getGroup(GroupId groupId) {
public Group getGroup(GroupId groupId) {
return toGroup(groupHelper.getGroup(groupId));
}
public GroupInfo getGroupInfo(GroupId groupId) {
return groupHelper.getGroup(groupId);
}
@Override
public List<IdentityInfo> getIdentities() {
return account.getIdentityKeyStore().getIdentities();
public List<Identity> getIdentities() {
return account.getIdentityKeyStore()
.getIdentities()
.stream()
.map(this::toIdentity)
.collect(Collectors.toList());
}
private Identity toIdentity(final IdentityInfo identityInfo) {
if (identityInfo == null) {
return null;
}
final var address = account.getRecipientStore().resolveRecipientAddress(identityInfo.getRecipientId());
return new Identity(address,
identityInfo.getIdentityKey(),
computeSafetyNumber(address.toSignalServiceAddress(), identityInfo.getIdentityKey()),
computeSafetyNumberForScanning(address.toSignalServiceAddress(), identityInfo.getIdentityKey()),
identityInfo.getTrustLevel(),
identityInfo.getDateAdded());
}
@Override
public List<IdentityInfo> getIdentities(RecipientIdentifier.Single recipient) {
public List<Identity> getIdentities(RecipientIdentifier.Single recipient) {
IdentityInfo identity;
try {
identity = account.getIdentityKeyStore().getIdentity(resolveRecipient(recipient));
} catch (UnregisteredUserException e) {
identity = null;
}
return identity == null ? List.of() : List.of(identity);
return identity == null ? List.of() : List.of(toIdentity(identity));
}
/**
@ -1144,8 +1202,7 @@ public class ManagerImpl implements Manager {
return fingerprint == null ? null : fingerprint.getDisplayableFingerprint().getDisplayText();
}
@Override
public byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
private byte[] computeSafetyNumberForScanning(SignalServiceAddress theirAddress, IdentityKey theirIdentityKey) {
final Fingerprint fingerprint = computeSafetyNumberFingerprint(theirAddress, theirIdentityKey);
return fingerprint == null ? null : fingerprint.getScannableFingerprint().getSerialized();
}
@ -1165,13 +1222,7 @@ public class ManagerImpl implements Manager {
return resolveSignalServiceAddress(resolveRecipient(address));
}
@Override
public SignalServiceAddress resolveSignalServiceAddress(UUID uuid) {
return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid));
}
@Override
public SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
private SignalServiceAddress resolveSignalServiceAddress(RecipientId recipientId) {
final var address = account.getRecipientStore().resolveRecipientAddress(recipientId);
if (address.getUuid().isPresent()) {
return address.toSignalServiceAddress();
@ -1180,13 +1231,15 @@ public class ManagerImpl implements Manager {
// Address in recipient store doesn't have a uuid, this shouldn't happen
// Try to retrieve the uuid from the server
final var number = address.getNumber().get();
final UUID uuid;
try {
return resolveSignalServiceAddress(getRegisteredUser(number));
uuid = getRegisteredUser(number);
} catch (IOException e) {
logger.warn("Failed to get uuid for e164 number: {}", number, e);
// Return SignalServiceAddress with unknown UUID
return address.toSignalServiceAddress();
}
return resolveSignalServiceAddress(account.getRecipientStore().resolveRecipient(uuid));
}
private Set<RecipientId> resolveRecipients(Collection<RecipientIdentifier.Single> recipients) throws UnregisteredUserException {

View file

@ -91,18 +91,18 @@ public class RegistrationManager implements Closeable {
}
public static RegistrationManager init(
String username, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
String number, File settingsPath, ServiceEnvironment serviceEnvironment, String userAgent
) throws IOException {
var pathConfig = PathConfig.createDefault(settingsPath);
final var serviceConfiguration = ServiceConfig.getServiceEnvironmentConfig(serviceEnvironment, userAgent);
if (!SignalAccount.userExists(pathConfig.getDataPath(), username)) {
if (!SignalAccount.userExists(pathConfig.getDataPath(), number)) {
var identityKey = KeyUtils.generateIdentityKeyPair();
var registrationId = KeyHelper.generateRegistrationId(false);
var profileKey = KeyUtils.createProfileKey();
var account = SignalAccount.create(pathConfig.getDataPath(),
username,
number,
identityKey,
registrationId,
profileKey,
@ -111,7 +111,7 @@ public class RegistrationManager implements Closeable {
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}
var account = SignalAccount.load(pathConfig.getDataPath(), username, true, TrustNewIdentity.ON_FIRST_USE);
var account = SignalAccount.load(pathConfig.getDataPath(), number, true, TrustNewIdentity.ON_FIRST_USE);
return new RegistrationManager(account, pathConfig, serviceConfiguration, userAgent);
}

View file

@ -4,16 +4,16 @@ import java.io.File;
public class UserAlreadyExists extends Exception {
private final String username;
private final String number;
private final File fileName;
public UserAlreadyExists(String username, File fileName) {
this.username = username;
public UserAlreadyExists(String number, File fileName) {
this.number = number;
this.fileName = fileName;
}
public String getUsername() {
return username;
public String getNumber() {
return number;
}
public File getFileName() {

View file

@ -6,12 +6,14 @@ public class Device {
private final String name;
private final long created;
private final long lastSeen;
private final boolean thisDevice;
public Device(long id, String name, long created, long lastSeen) {
public Device(long id, String name, long created, long lastSeen, final boolean thisDevice) {
this.id = id;
this.name = name;
this.created = created;
this.lastSeen = lastSeen;
this.thisDevice = thisDevice;
}
public long getId() {
@ -29,4 +31,8 @@ public class Device {
public long getLastSeen() {
return lastSeen;
}
public boolean isThisDevice() {
return thisDevice;
}
}

View file

@ -0,0 +1,99 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.groups.GroupInviteLinkUrl;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import java.util.Set;
public class Group {
private final GroupId groupId;
private final String title;
private final String description;
private final GroupInviteLinkUrl groupInviteLinkUrl;
private final Set<RecipientAddress> members;
private final Set<RecipientAddress> pendingMembers;
private final Set<RecipientAddress> requestingMembers;
private final Set<RecipientAddress> adminMembers;
private final boolean isBlocked;
private final int messageExpirationTime;
private final boolean isAnnouncementGroup;
private final boolean isMember;
public Group(
final GroupId groupId,
final String title,
final String description,
final GroupInviteLinkUrl groupInviteLinkUrl,
final Set<RecipientAddress> members,
final Set<RecipientAddress> pendingMembers,
final Set<RecipientAddress> requestingMembers,
final Set<RecipientAddress> adminMembers,
final boolean isBlocked,
final int messageExpirationTime,
final boolean isAnnouncementGroup,
final boolean isMember
) {
this.groupId = groupId;
this.title = title;
this.description = description;
this.groupInviteLinkUrl = groupInviteLinkUrl;
this.members = members;
this.pendingMembers = pendingMembers;
this.requestingMembers = requestingMembers;
this.adminMembers = adminMembers;
this.isBlocked = isBlocked;
this.messageExpirationTime = messageExpirationTime;
this.isAnnouncementGroup = isAnnouncementGroup;
this.isMember = isMember;
}
public GroupId getGroupId() {
return groupId;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public GroupInviteLinkUrl getGroupInviteLinkUrl() {
return groupInviteLinkUrl;
}
public Set<RecipientAddress> getMembers() {
return members;
}
public Set<RecipientAddress> getPendingMembers() {
return pendingMembers;
}
public Set<RecipientAddress> getRequestingMembers() {
return requestingMembers;
}
public Set<RecipientAddress> getAdminMembers() {
return adminMembers;
}
public boolean isBlocked() {
return isBlocked;
}
public int getMessageExpirationTime() {
return messageExpirationTime;
}
public boolean isAnnouncementGroup() {
return isAnnouncementGroup;
}
public boolean isMember() {
return isMember;
}
}

View file

@ -0,0 +1,65 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.TrustLevel;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.libsignal.IdentityKey;
import java.util.Date;
public class Identity {
private final RecipientAddress recipient;
private final IdentityKey identityKey;
private final String safetyNumber;
private final byte[] scannableSafetyNumber;
private final TrustLevel trustLevel;
private final Date dateAdded;
public Identity(
final RecipientAddress recipient,
final IdentityKey identityKey,
final String safetyNumber,
final byte[] scannableSafetyNumber,
final TrustLevel trustLevel,
final Date dateAdded
) {
this.recipient = recipient;
this.identityKey = identityKey;
this.safetyNumber = safetyNumber;
this.scannableSafetyNumber = scannableSafetyNumber;
this.trustLevel = trustLevel;
this.dateAdded = dateAdded;
}
public RecipientAddress getRecipient() {
return recipient;
}
public IdentityKey getIdentityKey() {
return this.identityKey;
}
public TrustLevel getTrustLevel() {
return this.trustLevel;
}
boolean isTrusted() {
return trustLevel == TrustLevel.TRUSTED_UNVERIFIED || trustLevel == TrustLevel.TRUSTED_VERIFIED;
}
public Date getDateAdded() {
return this.dateAdded;
}
public byte[] getFingerprint() {
return identityKey.getPublicKey().serialize();
}
public String getSafetyNumber() {
return safetyNumber;
}
public byte[] getScannableSafetyNumber() {
return scannableSafetyNumber;
}
}

View file

@ -1,6 +1,7 @@
package org.asamk.signal.manager.api;
import org.asamk.signal.manager.groups.GroupId;
import org.asamk.signal.manager.storage.recipients.RecipientAddress;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.InvalidNumberException;
import org.whispersystems.signalservice.api.util.PhoneNumberFormatter;
@ -29,6 +30,17 @@ public abstract class RecipientIdentifier {
public static Single fromAddress(SignalServiceAddress address) {
return new Uuid(address.getUuid());
}
public static Single fromAddress(RecipientAddress address) {
if (address.getNumber().isPresent()) {
return new Number(address.getNumber().get());
} else if (address.getUuid().isPresent()) {
return new Uuid(address.getUuid().get());
}
throw new AssertionError("RecipientAddress without identifier");
}
public abstract String getIdentifier();
}
public static class Uuid extends Single {
@ -53,6 +65,11 @@ public abstract class RecipientIdentifier {
public int hashCode() {
return uuid.hashCode();
}
@Override
public String getIdentifier() {
return uuid.toString();
}
}
public static class Number extends Single {
@ -77,6 +94,11 @@ public abstract class RecipientIdentifier {
public int hashCode() {
return number.hashCode();
}
@Override
public String getIdentifier() {
return number;
}
}
public static class Group extends RecipientIdentifier {

View file

@ -57,6 +57,16 @@ public class RecipientAddress {
}
}
public String getLegacyIdentifier() {
if (e164.isPresent()) {
return e164.get();
} else if (uuid.isPresent()) {
return uuid.get().toString();
} else {
throw new AssertionError("Given the checks in the constructor, this should not be possible.");
}
}
public boolean matches(RecipientAddress other) {
return (uuid.isPresent() && other.uuid.isPresent() && uuid.get().equals(other.uuid.get())) || (
e164.isPresent() && other.e164.isPresent() && e164.get().equals(other.e164.get())