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