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