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 setUsername(final String username) throws InvalidJidException {
198 jid = Jid.fromParts(username, jid.getDomainpart(), jid.getResourcepart());
199 }
200
201 public Jid getServer() {
202 return jid.toDomainJid();
203 }
204
205 public void setServer(final String server) throws InvalidJidException {
206 jid = Jid.fromParts(jid.getLocalpart(), server, jid.getResourcepart());
207 }
208
209 public String getPassword() {
210 return password;
211 }
212
213 public void setPassword(final String password) {
214 this.password = password;
215 }
216
217 public State getStatus() {
218 if (isOptionSet(OPTION_DISABLED)) {
219 return State.DISABLED;
220 } else {
221 return this.status;
222 }
223 }
224
225 public void setStatus(final State status) {
226 this.status = status;
227 }
228
229 public boolean errorStatus() {
230 return getStatus().isError();
231 }
232
233 public boolean hasErrorStatus() {
234 return getXmppConnection() != null && getStatus().isError() && getXmppConnection().getAttempt() >= 2;
235 }
236
237 public String getResource() {
238 return jid.getResourcepart();
239 }
240
241 public boolean setResource(final String resource) {
242 final String oldResource = jid.getResourcepart();
243 if (oldResource == null || !oldResource.equals(resource)) {
244 try {
245 jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
246 return true;
247 } catch (final InvalidJidException ignored) {
248 return true;
249 }
250 }
251 return false;
252 }
253
254 public Jid getJid() {
255 return jid;
256 }
257
258 public JSONObject getKeys() {
259 return keys;
260 }
261
262 public String getKey(final String name) {
263 return this.keys.optString(name, null);
264 }
265
266 public boolean setKey(final String keyName, final String keyValue) {
267 try {
268 this.keys.put(keyName, keyValue);
269 return true;
270 } catch (final JSONException e) {
271 return false;
272 }
273 }
274
275 @Override
276 public ContentValues getContentValues() {
277 final ContentValues values = new ContentValues();
278 values.put(UUID, uuid);
279 values.put(USERNAME, jid.getLocalpart());
280 values.put(SERVER, jid.getDomainpart());
281 values.put(PASSWORD, password);
282 values.put(OPTIONS, options);
283 values.put(KEYS, this.keys.toString());
284 values.put(ROSTERVERSION, rosterVersion);
285 values.put(AVATAR, avatar);
286 return values;
287 }
288
289 public AxolotlService getAxolotlService() {
290 return axolotlService;
291 }
292
293 public void initAccountServices(final XmppConnectionService context) {
294 this.mOtrService = new OtrService(context, this);
295 this.axolotlService = new AxolotlService(this, context);
296 }
297
298 public OtrService getOtrService() {
299 return this.mOtrService;
300 }
301
302 public XmppConnection getXmppConnection() {
303 return this.xmppConnection;
304 }
305
306 public void setXmppConnection(final XmppConnection connection) {
307 this.xmppConnection = connection;
308 }
309
310 public String getOtrFingerprint() {
311 if (this.otrFingerprint == null) {
312 try {
313 if (this.mOtrService == null) {
314 return null;
315 }
316 final PublicKey publicKey = this.mOtrService.getPublicKey();
317 if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
318 return null;
319 }
320 this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
321 return this.otrFingerprint;
322 } catch (final OtrCryptoException ignored) {
323 return null;
324 }
325 } else {
326 return this.otrFingerprint;
327 }
328 }
329
330 public String getRosterVersion() {
331 if (this.rosterVersion == null) {
332 return "";
333 } else {
334 return this.rosterVersion;
335 }
336 }
337
338 public void setRosterVersion(final String version) {
339 this.rosterVersion = version;
340 }
341
342 public int countPresences() {
343 return this.getRoster().getContact(this.getJid().toBareJid()).getPresences().size();
344 }
345
346 public String getPgpSignature() {
347 if (keys.has("pgp_signature")) {
348 try {
349 return keys.getString("pgp_signature");
350 } catch (final JSONException e) {
351 return null;
352 }
353 } else {
354 return null;
355 }
356 }
357
358 public Roster getRoster() {
359 return this.roster;
360 }
361
362 public List<Bookmark> getBookmarks() {
363 return this.bookmarks;
364 }
365
366 public void setBookmarks(final List<Bookmark> bookmarks) {
367 this.bookmarks = bookmarks;
368 }
369
370 public boolean hasBookmarkFor(final Jid conferenceJid) {
371 for (final Bookmark bookmark : this.bookmarks) {
372 final Jid jid = bookmark.getJid();
373 if (jid != null && jid.equals(conferenceJid.toBareJid())) {
374 return true;
375 }
376 }
377 return false;
378 }
379
380 public boolean setAvatar(final String filename) {
381 if (this.avatar != null && this.avatar.equals(filename)) {
382 return false;
383 } else {
384 this.avatar = filename;
385 return true;
386 }
387 }
388
389 public String getAvatar() {
390 return this.avatar;
391 }
392
393 public void activateGracePeriod() {
394 this.mEndGracePeriod = SystemClock.elapsedRealtime()
395 + (Config.CARBON_GRACE_PERIOD * 1000);
396 }
397
398 public void deactivateGracePeriod() {
399 this.mEndGracePeriod = 0L;
400 }
401
402 public boolean inGracePeriod() {
403 return SystemClock.elapsedRealtime() < this.mEndGracePeriod;
404 }
405
406 public String getShareableUri() {
407 final String fingerprint = this.getOtrFingerprint();
408 if (fingerprint != null) {
409 return "xmpp:" + this.getJid().toBareJid().toString() + "?otr-fingerprint="+fingerprint;
410 } else {
411 return "xmpp:" + this.getJid().toBareJid().toString();
412 }
413 }
414
415 public boolean isBlocked(final ListItem contact) {
416 final Jid jid = contact.getJid();
417 return jid != null && (blocklist.contains(jid.toBareJid()) || blocklist.contains(jid.toDomainJid()));
418 }
419
420 public boolean isBlocked(final Jid jid) {
421 return jid != null && blocklist.contains(jid.toBareJid());
422 }
423
424 public Collection<Jid> getBlocklist() {
425 return this.blocklist;
426 }
427
428 public void clearBlocklist() {
429 getBlocklist().clear();
430 }
431
432 public boolean isOnlineAndConnected() {
433 return this.getStatus() == State.ONLINE && this.getXmppConnection() != null;
434 }
435}