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