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