Account.java

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