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