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