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