Account.java

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