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