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(final Jid jid, final String password) {
166		this(java.util.UUID.randomUUID().toString(), jid,
167				password, 0, null, "", null, null, null, 5222);
168	}
169
170	private Account(final String uuid, final Jid jid,
171			final String password, final int options, final String rosterVersion, final String keys,
172			final String avatar, String displayName, String hostname, int port) {
173		this.uuid = uuid;
174		this.jid = jid;
175		if (jid.isBareJid()) {
176			this.setResource("mobile");
177		}
178		this.password = password;
179		this.options = options;
180		this.rosterVersion = rosterVersion;
181		try {
182			this.keys = new JSONObject(keys);
183		} catch (final JSONException ignored) {
184			this.keys = new JSONObject();
185		}
186		this.avatar = avatar;
187		this.displayName = displayName;
188		this.hostname = hostname;
189		this.port = port;
190	}
191
192	public static Account fromCursor(final Cursor cursor) {
193		Jid jid = null;
194		try {
195			jid = Jid.fromParts(cursor.getString(cursor.getColumnIndex(USERNAME)),
196					cursor.getString(cursor.getColumnIndex(SERVER)), "mobile");
197		} catch (final InvalidJidException ignored) {
198		}
199		return new Account(cursor.getString(cursor.getColumnIndex(UUID)),
200				jid,
201				cursor.getString(cursor.getColumnIndex(PASSWORD)),
202				cursor.getInt(cursor.getColumnIndex(OPTIONS)),
203				cursor.getString(cursor.getColumnIndex(ROSTERVERSION)),
204				cursor.getString(cursor.getColumnIndex(KEYS)),
205				cursor.getString(cursor.getColumnIndex(AVATAR)),
206				cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)),
207				cursor.getString(cursor.getColumnIndex(HOSTNAME)),
208				cursor.getInt(cursor.getColumnIndex(PORT)));
209	}
210
211	public boolean isOptionSet(final int option) {
212		return ((options & (1 << option)) != 0);
213	}
214
215	public void setOption(final int option, final boolean value) {
216		if (value) {
217			this.options |= 1 << option;
218		} else {
219			this.options &= ~(1 << option);
220		}
221	}
222
223	public String getUsername() {
224		return jid.getLocalpart();
225	}
226
227	public void setJid(final Jid jid) {
228		this.jid = jid;
229	}
230
231	public Jid getServer() {
232		return jid.toDomainJid();
233	}
234
235	public String getPassword() {
236		return password;
237	}
238
239	public void setPassword(final String password) {
240		this.password = password;
241	}
242
243	public void setHostname(String hostname) {
244		this.hostname = hostname;
245	}
246
247	public String getHostname() {
248		return this.hostname == null ? "" : this.hostname;
249	}
250
251	public boolean isOnion() {
252		return getServer().toString().toLowerCase().endsWith(".onion");
253	}
254
255	public void setPort(int port) {
256		this.port = port;
257	}
258
259	public int getPort() {
260		return this.port;
261	}
262
263	public State getStatus() {
264		if (isOptionSet(OPTION_DISABLED)) {
265			return State.DISABLED;
266		} else {
267			return this.status;
268		}
269	}
270
271	public void setStatus(final State status) {
272		this.status = status;
273	}
274
275	public boolean errorStatus() {
276		return getStatus().isError();
277	}
278
279	public boolean hasErrorStatus() {
280		return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 3;
281	}
282
283	public String getResource() {
284		return jid.getResourcepart();
285	}
286
287	public boolean setResource(final String resource) {
288		final String oldResource = jid.getResourcepart();
289		if (oldResource == null || !oldResource.equals(resource)) {
290			try {
291				jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
292				return true;
293			} catch (final InvalidJidException ignored) {
294				return true;
295			}
296		}
297		return false;
298	}
299
300	public Jid getJid() {
301		return jid;
302	}
303
304	public JSONObject getKeys() {
305		return keys;
306	}
307
308	public String getKey(final String name) {
309		return this.keys.optString(name, null);
310	}
311
312	public boolean setKey(final String keyName, final String keyValue) {
313		try {
314			this.keys.put(keyName, keyValue);
315			return true;
316		} catch (final JSONException e) {
317			return false;
318		}
319	}
320
321	public boolean setPrivateKeyAlias(String alias) {
322		return setKey("private_key_alias", alias);
323	}
324
325	public String getPrivateKeyAlias() {
326		return getKey("private_key_alias");
327	}
328
329	@Override
330	public ContentValues getContentValues() {
331		final ContentValues values = new ContentValues();
332		values.put(UUID, uuid);
333		values.put(USERNAME, jid.getLocalpart());
334		values.put(SERVER, jid.getDomainpart());
335		values.put(PASSWORD, password);
336		values.put(OPTIONS, options);
337		values.put(KEYS, this.keys.toString());
338		values.put(ROSTERVERSION, rosterVersion);
339		values.put(AVATAR, avatar);
340		values.put(DISPLAY_NAME, displayName);
341		values.put(HOSTNAME, hostname);
342		values.put(PORT, port);
343		return values;
344	}
345
346	public AxolotlService getAxolotlService() {
347		return axolotlService;
348	}
349
350	public void initAccountServices(final XmppConnectionService context) {
351		this.mOtrService = new OtrService(context, this);
352		this.axolotlService = new AxolotlService(this, context);
353		if (xmppConnection != null) {
354			xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
355		}
356		this.pgpDecryptionService = new PgpDecryptionService(context);
357	}
358
359	public OtrService getOtrService() {
360		return this.mOtrService;
361	}
362
363	public PgpDecryptionService getPgpDecryptionService() {
364		return pgpDecryptionService;
365	}
366
367	public XmppConnection getXmppConnection() {
368		return this.xmppConnection;
369	}
370
371	public void setXmppConnection(final XmppConnection connection) {
372		this.xmppConnection = connection;
373	}
374
375	public String getOtrFingerprint() {
376		if (this.otrFingerprint == null) {
377			try {
378				if (this.mOtrService == null) {
379					return null;
380				}
381				final PublicKey publicKey = this.mOtrService.getPublicKey();
382				if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
383					return null;
384				}
385				this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
386				return this.otrFingerprint;
387			} catch (final OtrCryptoException ignored) {
388				return null;
389			}
390		} else {
391			return this.otrFingerprint;
392		}
393	}
394
395	public String getRosterVersion() {
396		if (this.rosterVersion == null) {
397			return "";
398		} else {
399			return this.rosterVersion;
400		}
401	}
402
403	public void setRosterVersion(final String version) {
404		this.rosterVersion = version;
405	}
406
407	public int countPresences() {
408		return this.getRoster().getContact(this.getJid().toBareJid()).getPresences().size();
409	}
410
411	public String getPgpSignature() {
412		try {
413			if (keys.has(KEY_PGP_SIGNATURE) && !"null".equals(keys.getString(KEY_PGP_SIGNATURE))) {
414				return keys.getString(KEY_PGP_SIGNATURE);
415			} else {
416				return null;
417			}
418		} catch (final JSONException e) {
419			return null;
420		}
421	}
422
423	public boolean setPgpSignature(String signature) {
424		try {
425			keys.put(KEY_PGP_SIGNATURE, signature);
426		} catch (JSONException e) {
427			return false;
428		}
429		return true;
430	}
431
432	public boolean unsetPgpSignature() {
433		try {
434			keys.put(KEY_PGP_SIGNATURE, JSONObject.NULL);
435		} catch (JSONException e) {
436			return false;
437		}
438		return true;
439	}
440
441	public long getPgpId() {
442		if (keys.has(KEY_PGP_ID)) {
443			try {
444				return keys.getLong(KEY_PGP_ID);
445			} catch (JSONException e) {
446				return -1;
447			}
448		} else {
449			return -1;
450		}
451	}
452
453	public boolean setPgpSignId(long pgpID) {
454		try {
455			keys.put(KEY_PGP_ID, pgpID);
456		} catch (JSONException e) {
457			return false;
458		}
459		return true;
460	}
461
462	public Roster getRoster() {
463		return this.roster;
464	}
465
466	public List<Bookmark> getBookmarks() {
467		return this.bookmarks;
468	}
469
470	public void setBookmarks(final List<Bookmark> bookmarks) {
471		this.bookmarks = bookmarks;
472	}
473
474	public boolean hasBookmarkFor(final Jid conferenceJid) {
475		for (final Bookmark bookmark : this.bookmarks) {
476			final Jid jid = bookmark.getJid();
477			if (jid != null && jid.equals(conferenceJid.toBareJid())) {
478				return true;
479			}
480		}
481		return false;
482	}
483
484	public boolean setAvatar(final String filename) {
485		if (this.avatar != null && this.avatar.equals(filename)) {
486			return false;
487		} else {
488			this.avatar = filename;
489			return true;
490		}
491	}
492
493	public String getAvatar() {
494		return this.avatar;
495	}
496
497	public void activateGracePeriod() {
498		this.mEndGracePeriod = SystemClock.elapsedRealtime()
499			+ (Config.CARBON_GRACE_PERIOD * 1000);
500	}
501
502	public void deactivateGracePeriod() {
503		this.mEndGracePeriod = 0L;
504	}
505
506	public boolean inGracePeriod() {
507		return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
508	}
509
510	public String getShareableUri() {
511		final String fingerprint = this.getOtrFingerprint();
512		if (fingerprint != null) {
513			return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
514		} else {
515			return "xmpp:" + this.getJid().toBareJid().toString();
516		}
517	}
518
519	public boolean isBlocked(final ListItem contact) {
520		final Jid jid = contact.getJid();
521		return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid()));
522	}
523
524	public boolean isBlocked(final Jid jid) {
525		return jid != null && blocklist.contains(jid.toBareJid());
526	}
527
528	public Collection<Jid> getBlocklist() {
529		return this.blocklist;
530	}
531
532	public void clearBlocklist() {
533		getBlocklist().clear();
534	}
535
536	public boolean isOnlineAndConnected() {
537		return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
538	}
539}