Account.java

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