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