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