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}