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