1package eu.siacs.conversations.entities;
2
3import android.content.ContentValues;
4import android.database.Cursor;
5import android.os.SystemClock;
6
7import eu.siacs.conversations.crypto.PgpDecryptionService;
8import net.java.otr4j.crypto.OtrCryptoEngineImpl;
9import net.java.otr4j.crypto.OtrCryptoException;
10
11import org.json.JSONException;
12import org.json.JSONObject;
13
14import java.security.PublicKey;
15import java.security.interfaces.DSAPublicKey;
16import java.util.Collection;
17import java.util.List;
18import java.util.concurrent.CopyOnWriteArrayList;
19import java.util.concurrent.CopyOnWriteArraySet;
20
21import eu.siacs.conversations.Config;
22import eu.siacs.conversations.R;
23import eu.siacs.conversations.crypto.OtrService;
24import eu.siacs.conversations.crypto.axolotl.AxolotlService;
25import eu.siacs.conversations.services.XmppConnectionService;
26import eu.siacs.conversations.xmpp.XmppConnection;
27import eu.siacs.conversations.xmpp.jid.InvalidJidException;
28import eu.siacs.conversations.xmpp.jid.Jid;
29
30public class Account extends AbstractEntity {
31
32 public static final String TABLENAME = "accounts";
33
34 public static final String USERNAME = "username";
35 public static final String SERVER = "server";
36 public static final String PASSWORD = "password";
37 public static final String OPTIONS = "options";
38 public static final String ROSTERVERSION = "rosterversion";
39 public static final String KEYS = "keys";
40 public static final String AVATAR = "avatar";
41 public static final String DISPLAY_NAME = "display_name";
42
43 public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
44
45 public static final int OPTION_USETLS = 0;
46 public static final int OPTION_DISABLED = 1;
47 public static final int OPTION_REGISTER = 2;
48 public static final int OPTION_USECOMPRESSION = 3;
49
50 public boolean httpUploadAvailable() {
51 return xmppConnection != null && xmppConnection.getFeatures().httpUpload();
52 }
53
54 public void setDisplayName(String displayName) {
55 this.displayName = displayName;
56 }
57
58 public String getDisplayName() {
59 return displayName;
60 }
61
62 public static enum State {
63 DISABLED,
64 OFFLINE,
65 CONNECTING,
66 ONLINE,
67 NO_INTERNET,
68 UNAUTHORIZED(true),
69 SERVER_NOT_FOUND(true),
70 REGISTRATION_FAILED(true),
71 REGISTRATION_CONFLICT(true),
72 REGISTRATION_SUCCESSFUL,
73 REGISTRATION_NOT_SUPPORTED(true),
74 SECURITY_ERROR(true),
75 INCOMPATIBLE_SERVER(true),
76 DNS_TIMEOUT(true);
77
78 private final boolean isError;
79
80 public boolean isError() {
81 return this.isError;
82 }
83
84 private State(final boolean isError) {
85 this.isError = isError;
86 }
87
88 private State() {
89 this(false);
90 }
91
92 public int getReadableId() {
93 switch (this) {
94 case DISABLED:
95 return R.string.account_status_disabled;
96 case ONLINE:
97 return R.string.account_status_online;
98 case CONNECTING:
99 return R.string.account_status_connecting;
100 case OFFLINE:
101 return R.string.account_status_offline;
102 case UNAUTHORIZED:
103 return R.string.account_status_unauthorized;
104 case SERVER_NOT_FOUND:
105 return R.string.account_status_not_found;
106 case NO_INTERNET:
107 return R.string.account_status_no_internet;
108 case REGISTRATION_FAILED:
109 return R.string.account_status_regis_fail;
110 case REGISTRATION_CONFLICT:
111 return R.string.account_status_regis_conflict;
112 case REGISTRATION_SUCCESSFUL:
113 return R.string.account_status_regis_success;
114 case REGISTRATION_NOT_SUPPORTED:
115 return R.string.account_status_regis_not_sup;
116 case SECURITY_ERROR:
117 return R.string.account_status_security_error;
118 case INCOMPATIBLE_SERVER:
119 return R.string.account_status_incompatible_server;
120 case DNS_TIMEOUT:
121 return R.string.account_status_dns_timeout;
122 default:
123 return R.string.account_status_unknown;
124 }
125 }
126 }
127
128 public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>();
129 public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>();
130 protected Jid jid;
131 protected String password;
132 protected int options = 0;
133 protected String rosterVersion;
134 protected State status = State.OFFLINE;
135 protected JSONObject keys = new JSONObject();
136 protected String avatar;
137 protected String displayName = null;
138 protected boolean online = false;
139 private OtrService mOtrService = null;
140 private AxolotlService axolotlService = null;
141 private PgpDecryptionService pgpDecryptionService = null;
142 private XmppConnection xmppConnection = null;
143 private long mEndGracePeriod = 0L;
144 private String otrFingerprint;
145 private final Roster roster = new Roster(this);
146 private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
147 private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
148
149 public Account() {
150 this.uuid = "0";
151 }
152
153 public Account(final Jid jid, final String password) {
154 this(java.util.UUID.randomUUID().toString(), jid,
155 password, 0, null, "", null, null);
156 }
157
158 public Account(final String uuid, final Jid jid,
159 final String password, final int options, final String rosterVersion, final String keys,
160 final String avatar, String displayName) {
161 this.uuid = uuid;
162 this.jid = jid;
163 if (jid.isBareJid()) {
164 this.setResource("mobile");
165 }
166 this.password = password;
167 this.options = options;
168 this.rosterVersion = rosterVersion;
169 try {
170 this.keys = new JSONObject(keys);
171 } catch (final JSONException ignored) {
172 this.keys = new JSONObject();
173 }
174 this.avatar = avatar;
175 this.displayName = displayName;
176 }
177
178 public static Account fromCursor(final Cursor cursor) {
179 Jid jid = null;
180 try {
181 jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)),
182 cursor.getString(cursor.getColumnIndex(SERVER)), "mobile");
183 } catch (final InvalidJidException ignored) {
184 }
185 return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
186 jid,
187 cursor.getString(cursor.getColumnIndex(PASSWORD)),
188 cursor.getInt(cursor.getColumnIndex(OPTIONS)),
189 cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
190 cursor.getString(cursor.getColumnIndex(KEYS)),
191 cursor.getString(cursor.getColumnIndex(AVATAR)),
192 cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
193 }
194
195 public boolean isOptionSet(final int option) {
196 return ((options & (1 << option)) != 0);
197 }
198
199 public void setOption(final int option, final boolean value) {
200 if (value) {
201 this.options |= 1 << option;
202 } else {
203 this.options &= ~(1 << option);
204 }
205 }
206
207 public String getUsername() {
208 return jid.getLocalpart();
209 }
210
211 public void setJid(final Jid jid) {
212 this.jid = jid;
213 }
214
215 public Jid getServer() {
216 return jid.toDomainJid();
217 }
218
219 public String getPassword() {
220 return password;
221 }
222
223 public void setPassword(final String password) {
224 this.password = password;
225 }
226
227 public State getStatus() {
228 if (isOptionSet(OPTION_DISABLED)) {
229 return State.DISABLED;
230 } else {
231 return this.status;
232 }
233 }
234
235 public void setStatus(final State status) {
236 this.status = status;
237 }
238
239 public boolean errorStatus() {
240 return getStatus().isError();
241 }
242
243 public boolean hasErrorStatus() {
244 return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 2;
245 }
246
247 public String getResource() {
248 return jid.getResourcepart();
249 }
250
251 public boolean setResource(final String resource) {
252 final String oldResource = jid.getResourcepart();
253 if (oldResource == null || !oldResource.equals(resource)) {
254 try {
255 jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
256 return true;
257 } catch (final InvalidJidException ignored) {
258 return true;
259 }
260 }
261 return false;
262 }
263
264 public Jid getJid() {
265 return jid;
266 }
267
268 public JSONObject getKeys() {
269 return keys;
270 }
271
272 public String getKey(final String name) {
273 return this.keys.optString(name, null);
274 }
275
276 public boolean setKey(final String keyName, final String keyValue) {
277 try {
278 this.keys.put(keyName, keyValue);
279 return true;
280 } catch (final JSONException e) {
281 return false;
282 }
283 }
284
285 public boolean setPrivateKeyAlias(String alias) {
286 return setKey("private_key_alias", alias);
287 }
288
289 public String getPrivateKeyAlias() {
290 return getKey("private_key_alias");
291 }
292
293 @Override
294 public ContentValues getContentValues() {
295 final ContentValues values = new ContentValues();
296 values.put(UUID, uuid);
297 values.put(USERNAME, jid.getLocalpart());
298 values.put(SERVER, jid.getDomainpart());
299 values.put(PASSWORD, password);
300 values.put(OPTIONS, options);
301 values.put(KEYS, this.keys.toString());
302 values.put(ROSTERVERSION, rosterVersion);
303 values.put(AVATAR, avatar);
304 values.put(DISPLAY_NAME, displayName);
305 return values;
306 }
307
308 public AxolotlService getAxolotlService() {
309 return axolotlService;
310 }
311
312 public void initAccountServices(final XmppConnectionService context) {
313 this.mOtrService = new OtrService(context, this);
314 this.axolotlService = new AxolotlService(this, context);
315 if (xmppConnection != null) {
316 xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
317 }
318 this.pgpDecryptionService = new PgpDecryptionService(context);
319 }
320
321 public OtrService getOtrService() {
322 return this.mOtrService;
323 }
324
325 public PgpDecryptionService getPgpDecryptionService() {
326 return pgpDecryptionService;
327 }
328
329 public XmppConnection getXmppConnection() {
330 return this.xmppConnection;
331 }
332
333 public void setXmppConnection(final XmppConnection connection) {
334 this.xmppConnection = connection;
335 }
336
337 public String getOtrFingerprint() {
338 if (this.otrFingerprint == null) {
339 try {
340 if (this.mOtrService == null) {
341 return null;
342 }
343 final PublicKey publicKey = this.mOtrService.getPublicKey();
344 if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
345 return null;
346 }
347 this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
348 return this.otrFingerprint;
349 } catch (final OtrCryptoException ignored) {
350 return null;
351 }
352 } else {
353 return this.otrFingerprint;
354 }
355 }
356
357 public String getRosterVersion() {
358 if (this.rosterVersion == null) {
359 return "";
360 } else {
361 return this.rosterVersion;
362 }
363 }
364
365 public void setRosterVersion(final String version) {
366 this.rosterVersion = version;
367 }
368
369 public int countPresences() {
370 return this.getRoster().getContact(this.getJid().toBareJid()).getPresences().size();
371 }
372
373 public String getPgpSignature() {
374 if (keys.has("pgp_signature")) {
375 try {
376 return keys.getString("pgp_signature");
377 } catch (final JSONException e) {
378 return null;
379 }
380 } else {
381 return null;
382 }
383 }
384
385 public Roster getRoster() {
386 return this.roster;
387 }
388
389 public List<Bookmark> getBookmarks() {
390 return this.bookmarks;
391 }
392
393 public void setBookmarks(final List<Bookmark> bookmarks) {
394 this.bookmarks = bookmarks;
395 }
396
397 public boolean hasBookmarkFor(final Jid conferenceJid) {
398 for (final Bookmark bookmark : this.bookmarks) {
399 final Jid jid = bookmark.getJid();
400 if (jid != null && jid.equals(conferenceJid.toBareJid())) {
401 return true;
402 }
403 }
404 return false;
405 }
406
407 public boolean setAvatar(final String filename) {
408 if (this.avatar != null && this.avatar.equals(filename)) {
409 return false;
410 } else {
411 this.avatar = filename;
412 return true;
413 }
414 }
415
416 public String getAvatar() {
417 return this.avatar;
418 }
419
420 public void activateGracePeriod() {
421 this.mEndGracePeriod = SystemClock.elapsedRealtime()
422 + (Config.CARBON_GRACE_PERIOD * 1000);
423 }
424
425 public void deactivateGracePeriod() {
426 this.mEndGracePeriod = 0L;
427 }
428
429 public boolean inGracePeriod() {
430 return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
431 }
432
433 public String getShareableUri() {
434 final String fingerprint = this.getOtrFingerprint();
435 if (fingerprint != null) {
436 return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
437 } else {
438 return "xmpp:" + this.getJid().toBareJid().toString();
439 }
440 }
441
442 public boolean isBlocked(final ListItem contact) {
443 final Jid jid = contact.getJid();
444 return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid()));
445 }
446
447 public boolean isBlocked(final Jid jid) {
448 return jid != null && blocklist.contains(jid.toBareJid());
449 }
450
451 public Collection<Jid> getBlocklist() {
452 return this.blocklist;
453 }
454
455 public void clearBlocklist() {
456 getBlocklist().clear();
457 }
458
459 public boolean isOnlineAndConnected() {
460 return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
461 }
462}