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