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