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