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
325				&& (getStatus().isError() || getStatus() == State.CONNECTING)
326				&& getXmppConnection().getAttempt() >= 3;
327	}
328
329	public void setPresenceStatus(Presence.Status status) {
330		this.presenceStatus = status;
331	}
332
333	public Presence.Status getPresenceStatus() {
334		return this.presenceStatus;
335	}
336
337	public void setPresenceStatusMessage(String message) {
338		this.presenceStatusMessage = message;
339	}
340
341	public String getPresenceStatusMessage() {
342		return this.presenceStatusMessage;
343	}
344
345	public String getResource() {
346		return jid.getResourcepart();
347	}
348
349	public boolean setResource(final String resource) {
350		final String oldResource = jid.getResourcepart();
351		if (oldResource == null || !oldResource.equals(resource)) {
352			try {
353				jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
354				return true;
355			} catch (final InvalidJidException ignored) {
356				return true;
357			}
358		}
359		return false;
360	}
361
362	public Jid getJid() {
363		return jid;
364	}
365
366	public JSONObject getKeys() {
367		return keys;
368	}
369
370	public String getKey(final String name) {
371		return this.keys.optString(name, null);
372	}
373
374	public boolean setKey(final String keyName, final String keyValue) {
375		try {
376			this.keys.put(keyName, keyValue);
377			return true;
378		} catch (final JSONException e) {
379			return false;
380		}
381	}
382
383	public boolean setPrivateKeyAlias(String alias) {
384		return setKey("private_key_alias", alias);
385	}
386
387	public String getPrivateKeyAlias() {
388		return getKey("private_key_alias");
389	}
390
391	@Override
392	public ContentValues getContentValues() {
393		final ContentValues values = new ContentValues();
394		values.put(UUID, uuid);
395		values.put(USERNAME, jid.getLocalpart());
396		values.put(SERVER, jid.getDomainpart());
397		values.put(PASSWORD, password);
398		values.put(OPTIONS, options);
399		values.put(KEYS, this.keys.toString());
400		values.put(ROSTERVERSION, rosterVersion);
401		values.put(AVATAR, avatar);
402		values.put(DISPLAY_NAME, displayName);
403		values.put(HOSTNAME, hostname);
404		values.put(PORT, port);
405		values.put(STATUS, presenceStatus.toShowString());
406		values.put(STATUS_MESSAGE, presenceStatusMessage);
407		return values;
408	}
409
410	public AxolotlService getAxolotlService() {
411		return axolotlService;
412	}
413
414	public void initAccountServices(final XmppConnectionService context) {
415		this.mOtrService = new OtrService(context, this);
416		this.axolotlService = new AxolotlService(this, context);
417		this.pgpDecryptionService = new PgpDecryptionService(context);
418		if (xmppConnection != null) {
419			xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
420		}
421	}
422
423	public OtrService getOtrService() {
424		return this.mOtrService;
425	}
426
427	public PgpDecryptionService getPgpDecryptionService() {
428		return this.pgpDecryptionService;
429	}
430
431	public XmppConnection getXmppConnection() {
432		return this.xmppConnection;
433	}
434
435	public void setXmppConnection(final XmppConnection connection) {
436		this.xmppConnection = connection;
437	}
438
439	public String getOtrFingerprint() {
440		if (this.otrFingerprint == null) {
441			try {
442				if (this.mOtrService == null) {
443					return null;
444				}
445				final PublicKey publicKey = this.mOtrService.getPublicKey();
446				if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
447					return null;
448				}
449				this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
450				return this.otrFingerprint;
451			} catch (final OtrCryptoException ignored) {
452				return null;
453			}
454		} else {
455			return this.otrFingerprint;
456		}
457	}
458
459	public String getRosterVersion() {
460		if (this.rosterVersion == null) {
461			return "";
462		} else {
463			return this.rosterVersion;
464		}
465	}
466
467	public void setRosterVersion(final String version) {
468		this.rosterVersion = version;
469	}
470
471	public int countPresences() {
472		return this.getSelfContact().getPresences().size();
473	}
474
475	public String getPgpSignature() {
476		try {
477			if (keys.has(KEY_PGP_SIGNATURE) && !"null".equals(keys.getString(KEY_PGP_SIGNATURE))) {
478				return keys.getString(KEY_PGP_SIGNATURE);
479			} else {
480				return null;
481			}
482		} catch (final JSONException e) {
483			return null;
484		}
485	}
486
487	public boolean setPgpSignature(String signature) {
488		try {
489			keys.put(KEY_PGP_SIGNATURE, signature);
490		} catch (JSONException e) {
491			return false;
492		}
493		return true;
494	}
495
496	public boolean unsetPgpSignature() {
497		try {
498			keys.put(KEY_PGP_SIGNATURE, JSONObject.NULL);
499		} catch (JSONException e) {
500			return false;
501		}
502		return true;
503	}
504
505	public long getPgpId() {
506		if (keys.has(KEY_PGP_ID)) {
507			try {
508				return keys.getLong(KEY_PGP_ID);
509			} catch (JSONException e) {
510				return 0;
511			}
512		} else {
513			return 0;
514		}
515	}
516
517	public boolean setPgpSignId(long pgpID) {
518		try {
519			keys.put(KEY_PGP_ID, pgpID);
520		} catch (JSONException e) {
521			return false;
522		}
523		return true;
524	}
525
526	public Roster getRoster() {
527		return this.roster;
528	}
529
530	public List<Bookmark> getBookmarks() {
531		return this.bookmarks;
532	}
533
534	public void setBookmarks(final List<Bookmark> bookmarks) {
535		this.bookmarks = bookmarks;
536	}
537
538	public boolean hasBookmarkFor(final Jid conferenceJid) {
539		for (final Bookmark bookmark : this.bookmarks) {
540			final Jid jid = bookmark.getJid();
541			if (jid != null && jid.equals(conferenceJid.toBareJid())) {
542				return true;
543			}
544		}
545		return false;
546	}
547
548	public boolean setAvatar(final String filename) {
549		if (this.avatar != null && this.avatar.equals(filename)) {
550			return false;
551		} else {
552			this.avatar = filename;
553			return true;
554		}
555	}
556
557	public String getAvatar() {
558		return this.avatar;
559	}
560
561	public void activateGracePeriod(long duration) {
562		this.mEndGracePeriod = SystemClock.elapsedRealtime() + duration;
563	}
564
565	public void deactivateGracePeriod() {
566		this.mEndGracePeriod = 0L;
567	}
568
569	public boolean inGracePeriod() {
570		return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
571	}
572
573	public String getShareableUri() {
574		final String fingerprint = this.getOtrFingerprint();
575		if (fingerprint != null) {
576			return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
577		} else {
578			return "xmpp:" + this.getJid().toBareJid().toString();
579		}
580	}
581
582	public boolean isBlocked(final ListItem contact) {
583		final Jid jid = contact.getJid();
584		return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid()));
585	}
586
587	public boolean isBlocked(final Jid jid) {
588		return jid != null && blocklist.contains(jid.toBareJid());
589	}
590
591	public Collection<Jid> getBlocklist() {
592		return this.blocklist;
593	}
594
595	public void clearBlocklist() {
596		getBlocklist().clear();
597	}
598
599	public boolean isOnlineAndConnected() {
600		return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
601	}
602}