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