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