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