]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/helper/AccountHelper.java
Extract getSelfProfile method
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / helper / AccountHelper.java
1 package org.asamk.signal.manager.helper;
2
3 import org.asamk.signal.manager.DeviceLinkInfo;
4 import org.asamk.signal.manager.SignalDependencies;
5 import org.asamk.signal.manager.api.CaptchaRequiredException;
6 import org.asamk.signal.manager.api.IncorrectPinException;
7 import org.asamk.signal.manager.api.InvalidDeviceLinkException;
8 import org.asamk.signal.manager.api.PinLockedException;
9 import org.asamk.signal.manager.config.ServiceConfig;
10 import org.asamk.signal.manager.storage.SignalAccount;
11 import org.asamk.signal.manager.util.KeyUtils;
12 import org.asamk.signal.manager.util.NumberVerificationUtils;
13 import org.signal.libsignal.protocol.InvalidKeyException;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16 import org.whispersystems.signalservice.api.push.ACI;
17 import org.whispersystems.signalservice.api.push.PNI;
18 import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
19 import org.whispersystems.signalservice.api.util.DeviceNameUtil;
20
21 import java.io.IOException;
22 import java.util.Optional;
23 import java.util.concurrent.TimeUnit;
24
25 public class AccountHelper {
26
27 private final static Logger logger = LoggerFactory.getLogger(AccountHelper.class);
28
29 private final Context context;
30 private final SignalAccount account;
31 private final SignalDependencies dependencies;
32
33 private Callable unregisteredListener;
34
35 public AccountHelper(final Context context) {
36 this.account = context.getAccount();
37 this.dependencies = context.getDependencies();
38 this.context = context;
39 }
40
41 public void setUnregisteredListener(final Callable unregisteredListener) {
42 this.unregisteredListener = unregisteredListener;
43 }
44
45 public void checkAccountState() throws IOException {
46 if (account.getLastReceiveTimestamp() == 0) {
47 logger.info("The Signal protocol expects that incoming messages are regularly received.");
48 } else {
49 var diffInMilliseconds = System.currentTimeMillis() - account.getLastReceiveTimestamp();
50 long days = TimeUnit.DAYS.convert(diffInMilliseconds, TimeUnit.MILLISECONDS);
51 if (days > 7) {
52 logger.warn(
53 "Messages have been last received {} days ago. The Signal protocol expects that incoming messages are regularly received.",
54 days);
55 }
56 }
57 try {
58 context.getPreKeyHelper().refreshPreKeysIfNecessary();
59 if (account.getAci() == null || account.getPni() == null) {
60 checkWhoAmiI();
61 }
62 if (!account.isMasterDevice() && account.getPniIdentityKeyPair() == null) {
63 context.getSyncHelper().requestSyncPniIdentity();
64 }
65 updateAccountAttributes();
66 } catch (AuthorizationFailedException e) {
67 account.setRegistered(false);
68 throw e;
69 }
70 }
71
72 public void checkWhoAmiI() throws IOException {
73 final var whoAmI = dependencies.getAccountManager().getWhoAmI();
74 final var number = whoAmI.getNumber();
75 final var aci = ACI.parseOrNull(whoAmI.getAci());
76 final var pni = PNI.parseOrNull(whoAmI.getPni());
77 if (number.equals(account.getNumber()) && aci.equals(account.getAci()) && pni.equals(account.getPni())) {
78 return;
79 }
80
81 updateSelfIdentifiers(number, aci, pni);
82 }
83
84 private void updateSelfIdentifiers(final String number, final ACI aci, final PNI pni) {
85 account.setNumber(number);
86 account.setAci(aci);
87 account.setPni(pni);
88 account.getRecipientStore().resolveSelfRecipientTrusted(account.getSelfRecipientAddress());
89 // TODO check and update remote storage
90 context.getUnidentifiedAccessHelper().rotateSenderCertificates();
91 dependencies.resetAfterAddressChange();
92 dependencies.getSignalWebSocket().forceNewWebSockets();
93 context.getAccountFileUpdater().updateAccountIdentifiers(account.getNumber(), account.getAci());
94 }
95
96 public void startChangeNumber(
97 String newNumber, String captcha, boolean voiceVerification
98 ) throws IOException, CaptchaRequiredException {
99 final var accountManager = dependencies.createUnauthenticatedAccountManager(newNumber, account.getPassword());
100 NumberVerificationUtils.requestVerificationCode(accountManager, captcha, voiceVerification);
101 }
102
103 public void finishChangeNumber(
104 String newNumber, String verificationCode, String pin
105 ) throws IncorrectPinException, PinLockedException, IOException {
106 final var result = NumberVerificationUtils.verifyNumber(verificationCode,
107 pin,
108 context.getPinHelper(),
109 (verificationCode1, registrationLock) -> dependencies.getAccountManager()
110 .changeNumber(verificationCode1, newNumber, registrationLock));
111 // TODO handle response
112 updateSelfIdentifiers(newNumber, account.getAci(), PNI.parseOrThrow(result.first().getPni()));
113 }
114
115 public void setDeviceName(String deviceName) {
116 final var privateKey = account.getAciIdentityKeyPair().getPrivateKey();
117 final var encryptedDeviceName = DeviceNameUtil.encryptDeviceName(deviceName, privateKey);
118 account.setEncryptedDeviceName(encryptedDeviceName);
119 }
120
121 public void updateAccountAttributes() throws IOException {
122 dependencies.getAccountManager()
123 .setAccountAttributes(null,
124 account.getLocalRegistrationId(),
125 true,
126 null,
127 account.getPinMasterKey() == null ? null : account.getPinMasterKey().deriveRegistrationLock(),
128 account.getSelfUnidentifiedAccessKey(),
129 account.isUnrestrictedUnidentifiedAccess(),
130 ServiceConfig.capabilities,
131 account.isDiscoverableByPhoneNumber(),
132 account.getEncryptedDeviceName());
133 }
134
135 public void addDevice(DeviceLinkInfo deviceLinkInfo) throws IOException, InvalidDeviceLinkException {
136 var verificationCode = dependencies.getAccountManager().getNewDeviceVerificationCode();
137
138 try {
139 dependencies.getAccountManager()
140 .addDevice(deviceLinkInfo.deviceIdentifier(),
141 deviceLinkInfo.deviceKey(),
142 account.getAciIdentityKeyPair(),
143 account.getPniIdentityKeyPair(),
144 account.getProfileKey(),
145 verificationCode);
146 } catch (InvalidKeyException e) {
147 throw new InvalidDeviceLinkException("Invalid device link", e);
148 }
149 account.setMultiDevice(true);
150 }
151
152 public void removeLinkedDevices(int deviceId) throws IOException {
153 dependencies.getAccountManager().removeDevice(deviceId);
154 var devices = dependencies.getAccountManager().getDevices();
155 account.setMultiDevice(devices.size() > 1);
156 }
157
158 public void setRegistrationPin(String pin) throws IOException {
159 final var masterKey = account.getPinMasterKey() != null
160 ? account.getPinMasterKey()
161 : KeyUtils.createMasterKey();
162
163 context.getPinHelper().setRegistrationLockPin(pin, masterKey);
164
165 account.setRegistrationLockPin(pin, masterKey);
166 }
167
168 public void removeRegistrationPin() throws IOException {
169 // Remove KBS Pin
170 context.getPinHelper().removeRegistrationLockPin();
171
172 account.setRegistrationLockPin(null, null);
173 }
174
175 public void unregister() throws IOException {
176 // When setting an empty GCM id, the Signal-Server also sets the fetchesMessages property to false.
177 // If this is the master device, other users can't send messages to this number anymore.
178 // If this is a linked device, other users can still send messages, but this device doesn't receive them anymore.
179 dependencies.getAccountManager().setGcmId(Optional.empty());
180
181 account.setRegistered(false);
182 unregisteredListener.call();
183 }
184
185 public void deleteAccount() throws IOException {
186 try {
187 context.getPinHelper().removeRegistrationLockPin();
188 } catch (IOException e) {
189 logger.warn("Failed to remove registration lock pin");
190 }
191 account.setRegistrationLockPin(null, null);
192
193 dependencies.getAccountManager().deleteAccount();
194
195 account.setRegistered(false);
196 unregisteredListener.call();
197 }
198
199 public interface Callable {
200
201 void call();
202 }
203 }