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