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