• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

opensrp / opensrp-client-core / #174

pending completion
#174

Pull #921

github-actions

web-flow
Merge 66d3a69a9 into f9f6e7f97
Pull Request #921: Refactor user settings task

18309 of 26768 relevant lines covered (68.4%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

51.45
opensrp-core/src/main/java/org/smartregister/service/UserService.java
1
package org.smartregister.service;
2

3
import static org.smartregister.AllConstants.ENGLISH_LANGUAGE;
4
import static org.smartregister.AllConstants.ENGLISH_LOCALE;
5
import static org.smartregister.AllConstants.JURISDICTION_IDS;
6
import static org.smartregister.AllConstants.KANNADA_LANGUAGE;
7
import static org.smartregister.AllConstants.KANNADA_LOCALE;
8
import static org.smartregister.AllConstants.OPENSRP_AUTH_USER_URL_PATH;
9
import static org.smartregister.AllConstants.OPENSRP_LOCATION_URL_PATH;
10
import static org.smartregister.AllConstants.OPERATIONAL_AREAS;
11
import static org.smartregister.AllConstants.ORGANIZATION_IDS;
12
import static org.smartregister.event.Event.ON_LOGOUT;
13

14
import android.annotation.TargetApi;
15
import android.os.Build;
16
import android.os.Bundle;
17
import android.security.KeyPairGeneratorSpec;
18
import android.util.Base64;
19

20
import androidx.annotation.VisibleForTesting;
21

22
import org.apache.commons.lang3.StringUtils;
23
import org.smartregister.BuildConfig;
24
import org.smartregister.CoreLibrary;
25
import org.smartregister.DristhiConfiguration;
26
import org.smartregister.account.AccountHelper;
27
import org.smartregister.domain.LoginResponse;
28
import org.smartregister.domain.Response;
29
import org.smartregister.domain.TimeStatus;
30
import org.smartregister.domain.jsonmapping.LoginResponseData;
31
import org.smartregister.domain.jsonmapping.Time;
32
import org.smartregister.domain.jsonmapping.User;
33
import org.smartregister.domain.jsonmapping.util.LocationTree;
34
import org.smartregister.domain.jsonmapping.util.TeamLocation;
35
import org.smartregister.domain.jsonmapping.util.TeamMember;
36
import org.smartregister.repository.AllSettings;
37
import org.smartregister.repository.AllSharedPreferences;
38
import org.smartregister.security.PasswordHash;
39
import org.smartregister.security.SecurityHelper;
40
import org.smartregister.sync.SaveANMLocationTask;
41
import org.smartregister.sync.SaveANMTeamTask;
42
import org.smartregister.sync.SaveUserInfoTask;
43
import org.smartregister.util.AssetHandler;
44
import org.smartregister.util.CredentialsHelper;
45
import org.smartregister.util.Session;
46
import org.smartregister.view.activity.DrishtiApplication;
47

48
import java.io.ByteArrayInputStream;
49
import java.io.ByteArrayOutputStream;
50
import java.io.IOException;
51
import java.math.BigInteger;
52
import java.security.KeyPairGenerator;
53
import java.security.KeyStore;
54
import java.security.KeyStoreException;
55
import java.security.NoSuchAlgorithmException;
56
import java.security.cert.CertificateException;
57
import java.security.interfaces.RSAPrivateKey;
58
import java.security.interfaces.RSAPublicKey;
59
import java.text.SimpleDateFormat;
60
import java.util.ArrayList;
61
import java.util.Arrays;
62
import java.util.Calendar;
63
import java.util.Date;
64
import java.util.List;
65
import java.util.Locale;
66
import java.util.Set;
67
import java.util.TimeZone;
68
import java.util.stream.Collectors;
69

70
import javax.crypto.Cipher;
71
import javax.crypto.CipherInputStream;
72
import javax.crypto.CipherOutputStream;
73
import javax.security.auth.x500.X500Principal;
74

75
import timber.log.Timber;
76

77
public class UserService {
78
    private static final String KEYSTORE = "AndroidKeyStore";
79
    private static final String CIPHER = "RSA/ECB/PKCS1Padding";
80
    private static final String CIPHER_PROVIDER = "AndroidOpenSSL";
81

82
    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.ENGLISH);
1✔
83

84
    private final AllSettings allSettings;
85
    private final AllSharedPreferences allSharedPreferences;
86
    private HTTPAgent httpAgent;
87
    private Session session;
88
    private DristhiConfiguration configuration;
89
    private KeyStore keyStore;
90

91
    public UserService(AllSettings allSettingsArg, AllSharedPreferences
92
            allSharedPreferencesArg, HTTPAgent httpAgentArg, Session sessionArg,
93
                       DristhiConfiguration configurationArg) {
1✔
94
        allSettings = allSettingsArg;
1✔
95
        allSharedPreferences = allSharedPreferencesArg;
1✔
96
        httpAgent = httpAgentArg;
1✔
97
        session = sessionArg;
1✔
98
        configuration = configurationArg;
1✔
99
        initKeyStore();
1✔
100
    }
1✔
101

102
    private static TimeZone getDeviceTimeZone() {
103
        return TimeZone.getDefault();
1✔
104
    }
105

106
    private static Date getDeviceTime() {
107
        return Calendar.getInstance().getTime();
1✔
108
    }
109

110
    public static TimeZone getServerTimeZone(LoginResponseData userInfo) {
111
        if (userInfo != null) {
1✔
112
            try {
113
                Time time = userInfo.time;
1✔
114
                if (time != null) {
1✔
115
                    TimeZone timeZone = TimeZone.getTimeZone(time.getTimeZone());
1✔
116
                    return timeZone;
1✔
117
                }
118
            } catch (Exception e) {
×
119
                Timber.e(e);
×
120
            }
1✔
121
        }
122

123
        return null;
1✔
124
    }
125

126
    private static Date getServerTime(LoginResponseData userInfo) {
127
        if (userInfo != null) {
1✔
128
            try {
129
                Time time = userInfo.time;
1✔
130
                if (time != null) {
1✔
131
                    return DATE_FORMAT.parse(time.getTime());
1✔
132
                }
133
            } catch (Exception e) {
×
134
                Timber.e(e);
×
135
            }
1✔
136
        }
137

138
        return null;
1✔
139
    }
140

141
    public void initKeyStore() {
142
        try {
143
            this.keyStore = KeyStore.getInstance(KEYSTORE);
×
144
            this.keyStore.load(null);
×
145
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException |
1✔
146
                CertificateException e) {
147
            Timber.e(e);
1✔
148
        }
×
149
    }
1✔
150

151
    public TimeStatus validateStoredServerTimeZone() {
152
        TimeStatus result = TimeStatus.ERROR;
1✔
153
        try {
154
            String serverTimeZoneId = allSharedPreferences.fetchServerTimeZone();
1✔
155
            if (serverTimeZoneId != null) {
1✔
156
                TimeZone serverTimeZone = TimeZone.getTimeZone(serverTimeZoneId);
1✔
157
                TimeZone deviceTimeZone = TimeZone.getDefault();
1✔
158
                if (serverTimeZone != null && deviceTimeZone != null
1✔
159
                        && serverTimeZone.getRawOffset() == deviceTimeZone.getRawOffset()) {
1✔
160
                    result = TimeStatus.OK;
1✔
161
                } else {
162
                    result = TimeStatus.TIMEZONE_MISMATCH;
1✔
163
                }
164
            }
165
        } catch (Exception e) {
×
166
            Timber.e(e);
×
167
        }
1✔
168

169
        if (!result.equals(TimeStatus.OK)) {
1✔
170
            forceRemoteLogin(allSharedPreferences.fetchRegisteredANM());
1✔
171
        }
172

173
        return result;
1✔
174
    }
175

176
    private void saveServerTimeZone(LoginResponseData userInfo) {
177
        TimeZone serverTimeZone = getServerTimeZone(userInfo);
1✔
178
        String timeZoneId = null;
1✔
179
        if (serverTimeZone != null) {
1✔
180
            timeZoneId = serverTimeZone.getID();
×
181
        }
182

183
        allSharedPreferences.saveServerTimeZone(timeZoneId);
1✔
184
    }
1✔
185

186
    public TimeStatus validateDeviceTime(LoginResponseData userInfo, long serverTimeThreshold) {
187
        TimeZone serverTimeZone = getServerTimeZone(userInfo);
1✔
188
        TimeZone deviceTimeZone = getDeviceTimeZone();
1✔
189
        Date serverTime = getServerTime(userInfo);
1✔
190
        Date deviceTime = getDeviceTime();
1✔
191

192
        if (serverTimeZone != null && deviceTimeZone != null && serverTime != null && deviceTime != null) {
1✔
193
            if (serverTimeZone.getRawOffset() == deviceTimeZone.getRawOffset()) {
1✔
194
                long timeDiff = Math.abs(serverTime.getTime() - deviceTime.getTime());
1✔
195
                if (timeDiff <= serverTimeThreshold) {
1✔
196
                    return TimeStatus.OK;
1✔
197
                } else {
198
                    return TimeStatus.TIME_MISMATCH;
1✔
199
                }
200
            } else {
201
                return TimeStatus.TIMEZONE_MISMATCH;
1✔
202
            }
203
        }
204

205
        return TimeStatus.ERROR;
1✔
206
    }
207

208
    public boolean isValidLocalLogin(String userName, byte[] password) {
209
        return allSharedPreferences.fetchRegisteredANM().equals(userName) && DrishtiApplication.getInstance().getRepository()
1✔
210
                .canUseThisPassword(password) && !allSharedPreferences.fetchForceRemoteLogin(userName);
1✔
211
    }
212

213
    public boolean isUserInValidGroup(final String userName, final char[] password) {
214
        // Check if everything OK for local login
215
        if (keyStore != null && userName != null && password != null && !allSharedPreferences.fetchForceRemoteLogin(userName)) {
1✔
216

217
            byte[] storedHash = null;
1✔
218
            byte[] passwordHash = null;
1✔
219
            try {
220

221
                // Compare stored password hash with provided password hash
222
                storedHash = getLocalAuthenticationCredentials(userName);
1✔
223

224
                passwordHash = generatePasswordHash(userName, password);
1✔
225

226
                if (storedHash != null && Arrays.equals(storedHash, passwordHash)) {
1✔
227

228
                    return isValidDBPassword(getDBAuthenticationCredentials(userName));
1✔
229
                }
230
            } catch (Exception e) {
×
231
                Timber.e(e);
×
232
            } finally {
233
                SecurityHelper.clearArray(password);
1✔
234
                SecurityHelper.clearArray(passwordHash);
1✔
235
                SecurityHelper.clearArray(storedHash);
1✔
236
            }
237
        }
238

239
        return false;
1✔
240
    }
241

242
    @VisibleForTesting
243
    protected byte[] generatePasswordHash(String userName, char[] password) {
244
        byte[] passwordSalt = null;
1✔
245
        try {
246
            passwordSalt = SecurityHelper.nullSafeBase64Decode(AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType()));
1✔
247
            return SecurityHelper.hashPassword(password, passwordSalt);
×
248
        } catch (Exception e) {
1✔
249
            Timber.e(e);
1✔
250
        } finally {
251
            SecurityHelper.clearArray(passwordSalt);
1✔
252
        }
253

254
        return null;
1✔
255
    }
256

257

258
    @VisibleForTesting
259
    protected byte[] getLocalAuthenticationCredentials(String username) {
260
        return DrishtiApplication.getInstance().credentialsProvider().getCredentials(username, CredentialsHelper.CREDENTIALS_TYPE.LOCAL_AUTH);
1✔
261
    }
262

263
    @VisibleForTesting
264
    protected byte[] getDBAuthenticationCredentials(String username) {
265
        return DrishtiApplication.getInstance().credentialsProvider().getCredentials(username, CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH);
×
266
    }
267

268

269
    private boolean isValidDBPassword(byte[] password) {
270
        return DrishtiApplication.getInstance().getRepository().canUseThisPassword(password);
1✔
271
    }
272

273
    public byte[] getDecryptedAccountValue(String userName, String key) {
274
        if (keyStore != null && userName != null) {
1✔
275
            try {
276
                KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName);
×
277
                return getDecryptedAccountValue(userName, privateKeyEntry, key);
×
278
            } catch (Exception e) {
×
279
                Timber.e(e);
×
280
            }
281
        }
282
        return null;
1✔
283
    }
284

285
    private byte[] getDecryptedAccountValue(String userName, KeyStore.PrivateKeyEntry privateKeyEntry, String key) {
286
        if (privateKeyEntry != null) {
×
287

288
            String encryptedSecretKey = AccountHelper.getAccountManagerValue(key, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType());
×
289

290
            if (encryptedSecretKey != null) {
×
291
                try {
292
                    return decryptString(privateKeyEntry, encryptedSecretKey);
×
293
                } catch (Exception e) {
×
294
                    Timber.e(e);
×
295
                }
296
            }
297
        }
298
        return null;
×
299
    }
300

301
    public List<String> getUserRoles(String userName) {
302
        String roles = AccountHelper.getAccountManagerValue(AccountHelper.INTENT_KEY.ACCOUNT_ROLES, userName, CoreLibrary.getInstance().getAccountAuthenticatorXml().getAccountType());
×
303
        return Arrays.asList(roles);
×
304
    }
305

306
    public byte[] getDecryptedPassphraseValue(String userName) {
307
        if (keyStore != null && userName != null) {
1✔
308
            try {
309
                KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName);
×
310
                return decryptString(privateKeyEntry, allSharedPreferences.getPassphrase(CoreLibrary.getInstance().getSyncConfiguration().getEncryptionParam().name(), userName));
×
311
            } catch (Exception e) {
×
312
                Timber.e(e);
×
313
            }
314
        }
315
        return null;
1✔
316
    }
317

318
    /**
319
     * Checks whether the groupId for the provided user is the same as the first person to
320
     * successfully login
321
     *
322
     * @param userName The user to check
323
     * @return TRUE if the groupIds match
324
     */
325
    public boolean isUserInPioneerGroup(String userName) {
326
        String pioneerUser = allSharedPreferences.fetchPioneerUser();
1✔
327
        if (userName.equals(pioneerUser)) {
1✔
328
            return true;
1✔
329
        } else {
330
            byte[] currentUserSecretKey = getDecryptedPassphraseValue(userName);
1✔
331
            byte[] pioneerUserSecretKey = getDecryptedPassphraseValue(pioneerUser);
1✔
332

333
            if (currentUserSecretKey != null && Arrays.equals(pioneerUserSecretKey, currentUserSecretKey)) {
1✔
334
                return isValidDBPassword(currentUserSecretKey);
×
335
            }
336
        }
337

338
        return false;
1✔
339
    }
340

341
    public LoginResponse fetchUserDetails(String accessToken) {
342
        String requestURL;
343

344
        requestURL = configuration.dristhiBaseURL() + OPENSRP_AUTH_USER_URL_PATH;
×
345

346
        LoginResponse loginResponse = httpAgent.fetchUserDetails(requestURL, accessToken);
×
347

348
        return loginResponse;
×
349
    }
350

351
    public AllSharedPreferences getAllSharedPreferences() {
352
        return allSharedPreferences;
1✔
353
    }
354

355
    public Response<String> getLocationInformation() {
356
        String requestURL = configuration.dristhiBaseURL() + OPENSRP_LOCATION_URL_PATH;
1✔
357
        return httpAgent.fetch(requestURL);
1✔
358
    }
359

360
    public boolean localLoginWith(String userName) {
361
        boolean loginSuccessful = true;
1✔
362

363
        try {
364

365
            byte[] secretKey = getDecryptedPassphraseValue(userName);
1✔
366
            if (secretKey != null) {
1✔
367
                setupContextForLogin(secretKey);
×
368

369
                if (!allSharedPreferences.fetchRegisteredANM().equalsIgnoreCase(userName)) {
×
370
                    allSharedPreferences.updateANMUserName(userName);
×
371
                }
372

373
                DrishtiApplication.getInstance().getRepository().getReadableDatabase();
×
374
            } else
375
                return false;
1✔
376

377
        } catch (Exception e) {
×
378
            Timber.e(e);
×
379
            loginSuccessful = false;
×
380
        }
×
381

382
        return loginSuccessful;
×
383
    }
384

385
    public void processLoginResponseDataForUser(String userName, LoginResponseData userInfo) {
386
        String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername())
1✔
387
                ? userInfo.user.getUsername() : userName;
1✔
388
        boolean loginSuccessful = localLoginWith(username);
1✔
389
        saveAnmLocation(getUserLocation(userInfo));
1✔
390
        saveAnmTeam(getUserTeam(userInfo));
1✔
391
        saveUserInfo(getUserData(userInfo));
1✔
392
        saveDefaultLocationId(username, getUserDefaultLocationId(userInfo));
1✔
393
        saveUserLocationId(username, getUserLocationId(userInfo));
1✔
394
        saveDefaultTeam(username, getUserDefaultTeam(userInfo));
1✔
395
        saveDefaultTeamId(username, getUserDefaultTeamId(userInfo));
1✔
396
        saveServerTimeZone(userInfo);
1✔
397
        saveJurisdictions(userInfo.jurisdictions);
1✔
398
        saveJurisdictionIds(userInfo.jurisdictionIds);
1✔
399
        saveOrganizations(getUserTeam(userInfo));
1✔
400
        saveUserId(userName, userInfo.user.getBaseEntityId());
1✔
401
        if (loginSuccessful &&
1✔
402
                (StringUtils.isBlank(getUserDefaultLocationId(userInfo)) ||
×
403
                        StringUtils.isNotBlank(allSharedPreferences.fetchDefaultLocalityId(username))) &&
×
404
                (StringUtils.isBlank(getUserDefaultTeamId(userInfo)) ||
×
405
                        StringUtils.isNotBlank(allSharedPreferences.fetchDefaultTeamId(username))) &&
×
406
                (getUserLocation(userInfo) != null ||
×
407
                        StringUtils.isNotBlank(allSettings.fetchANMLocation())))
×
408
            allSharedPreferences.saveForceRemoteLogin(false, username);
×
409
    }
1✔
410

411
    public void forceRemoteLogin(String userName) {
412
        allSharedPreferences.saveForceRemoteLogin(true, userName);
1✔
413
    }
1✔
414

415
    public User getUserData(LoginResponseData userInfo) {
416
        try {
417
            if (userInfo != null) {
1✔
418
                return userInfo.user;
1✔
419
            }
420
        } catch (Exception e) {
×
421
            Timber.e(e);
×
422
        }
×
423
        return null;
×
424
    }
425

426
    public LocationTree getUserLocation(LoginResponseData userInfo) {
427
        try {
428
            if (userInfo != null) {
1✔
429
                return userInfo.locations;
1✔
430
            }
431
        } catch (Exception e) {
×
432
            Timber.e(e);
×
433
        }
×
434
        return null;
×
435
    }
436

437
    public TeamMember getUserTeam(LoginResponseData userInfo) {
438
        try {
439
            if (userInfo != null) {
1✔
440
                return userInfo.team;
1✔
441
            }
442
        } catch (Exception e) {
×
443
            Timber.e(e);
×
444
        }
×
445
        return null;
×
446
    }
447

448
    public void saveDefaultLocationId(String userName, String locationId) {
449
        if (userName != null) {
1✔
450
            allSharedPreferences.saveDefaultLocalityId(userName, locationId);
1✔
451
        }
452
    }
1✔
453

454
    public String getUserDefaultTeam(LoginResponseData userInfo) {
455
        try {
456
            if (userInfo != null && userInfo.team != null && userInfo.team.team != null) {
1✔
457
                return userInfo.team.team.teamName;
×
458
            }
459
        } catch (Exception e) {
×
460
            Timber.e(e);
×
461
        }
1✔
462
        return null;
1✔
463
    }
464

465
    public void saveDefaultTeam(String userName, String team) {
466
        if (userName != null) {
1✔
467
            allSharedPreferences.saveDefaultTeam(userName, team);
1✔
468
        }
469
    }
1✔
470

471
    public String getUserDefaultTeamId(LoginResponseData userInfo) {
472
        try {
473
            if (userInfo != null && userInfo.team != null && userInfo.team.team != null) {
1✔
474
                return userInfo.team.team.uuid;
×
475
            }
476
        } catch (Exception e) {
×
477
            Timber.e(e);
×
478
        }
1✔
479

480
        return null;
1✔
481
    }
482

483
    public void saveDefaultTeamId(String userName, String teamId) {
484
        if (userName != null) {
1✔
485
            allSharedPreferences.saveDefaultTeamId(userName, teamId);
1✔
486
        }
487
    }
1✔
488

489
    public String getUserDefaultLocationId(LoginResponseData userInfo) {
490
        try {
491
            if (userInfo != null && userInfo.team != null && userInfo.team.team != null && userInfo.team.team.location != null) {
1✔
492
                return userInfo.team.team.location.uuid;
×
493
            }
494
        } catch (Exception e) {
×
495
            Timber.e(e);
×
496
        }
1✔
497
        return null;
1✔
498
    }
499

500
    public String getUserLocationId(LoginResponseData userInfo) {
501
        try {
502
            if (userInfo != null && userInfo.team != null && userInfo.team.locations != null && !userInfo.team.locations.isEmpty()) {
1✔
503
                for (TeamLocation teamLocation : userInfo.team.locations) {
×
504
                    if (teamLocation != null) {
×
505
                        return teamLocation.uuid;
×
506
                    }
507
                }
×
508
            }
509
        } catch (Exception e) {
×
510
            Timber.e(e);
×
511
        }
1✔
512
        return null;
1✔
513
    }
514

515
    public void saveUserLocationId(String userName, String locationId) {
516
        if (userName != null) {
1✔
517
            allSharedPreferences.saveUserLocalityId(userName, locationId);
1✔
518
        }
519
    }
1✔
520

521
    public void saveAnmLocation(LocationTree anmLocation) {
522
        String anmLocationString = AssetHandler.javaToJsonString(anmLocation);
1✔
523
        executeSaveAnmLocationTask(allSettings, anmLocationString);
1✔
524
    }
1✔
525

526
    protected void executeSaveAnmLocationTask(AllSettings allSettings, String anmLocationString) {
527
        new SaveANMLocationTask(allSettings).execute(anmLocationString);
1✔
528
    }
1✔
529

530
    public void saveAnmTeam(TeamMember anmTeam) {
531
        String anmTeamString = AssetHandler.javaToJsonString(anmTeam);
1✔
532
        executeSaveANMTeamTask(allSettings, anmTeamString);
1✔
533
    }
1✔
534

535
    protected void executeSaveANMTeamTask(AllSettings allSettings, String anmLocationString) {
536
        new SaveANMTeamTask(allSettings).execute(anmLocationString);
1✔
537
    }
1✔
538

539
    public void saveJurisdictions(List<String> jurisdictions) {
540
        if (jurisdictions != null && !jurisdictions.isEmpty())
1✔
541
            allSharedPreferences.savePreference(OPERATIONAL_AREAS, android.text.TextUtils.join(",", jurisdictions));
×
542
    }
1✔
543

544
    public void saveJurisdictionIds(Set<String> jurisdictionIds) {
545
        if (jurisdictionIds != null && !jurisdictionIds.isEmpty())
1✔
546
            allSharedPreferences.savePreference(JURISDICTION_IDS, android.text.TextUtils.join(",", jurisdictionIds));
×
547
    }
1✔
548

549
    public Set<String> fetchJurisdictionIds() {
550
        String jurisdictionIds = allSharedPreferences.getPreference(JURISDICTION_IDS);
×
551
        return Arrays.stream(StringUtils.split(jurisdictionIds, ",")).collect(Collectors.toSet());
×
552
    }
553

554
    public void saveOrganizations(TeamMember teamMember) {
555
        if (teamMember != null && teamMember.team != null) {
1✔
556
            List<Long> organizations = teamMember.team.organizationIds;
×
557
            if (organizations != null && !organizations.isEmpty())
×
558
                saveOrganizations(organizations);
×
559
        }
560
    }
1✔
561

562
    public void saveOrganizations(List<Long> organizations) {
563
        allSharedPreferences.savePreference(ORGANIZATION_IDS, android.text.TextUtils.join(",", organizations));
×
564
    }
×
565

566
    public Set<Long> fetchOrganizations() {
567
        String organizationIds = allSharedPreferences.getPreference(ORGANIZATION_IDS);
×
568
        return Arrays.stream(StringUtils.split(organizationIds, ",")).map(Long::parseLong).collect(Collectors.toSet());
×
569
    }
570

571
    public void saveUserInfo(User user) {
572
        try {
573
            if (user != null && user.getPreferredName() != null) {
1✔
574
                String preferredName = user.getPreferredName();
1✔
575
                String userName = user.getUsername();
1✔
576
                allSharedPreferences.updateANMPreferredName(userName, preferredName);
1✔
577
            }
578
        } catch (Exception e) {
×
579
            Timber.e(e);
×
580
        }
1✔
581

582
        String userInfoString = AssetHandler.javaToJsonString(user);
1✔
583
        executeSaveUserInfoTask(allSettings, userInfoString);
1✔
584
    }
1✔
585

586
    protected void executeSaveUserInfoTask(AllSettings allSettings, String userInfoString) {
587
        new SaveUserInfoTask(allSettings).execute(userInfoString);
1✔
588
    }
1✔
589

590
    /**
591
     * Saves the a user's groupId and password in . GroupId and password
592
     * are not saved if groupId could not be found in userInfo
593
     *
594
     * @param userName The username you want to save the password and groupId
595
     * @param password The user's password
596
     * @param userInfo The user's info from the
597
     *                 endpoint (should contain the {team}.{team}.{uuid} key)
598
     */
599
    public Bundle saveUserCredentials(String userName, char[] password, LoginResponseData userInfo) {
600
        Bundle bundle = new Bundle();
×
601

602
        String username = userInfo.user != null && StringUtils.isNotBlank(userInfo.user.getUsername()) ? userInfo.user.getUsername() : userName;
×
603
        bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_NAME, username);
×
604

605
        if (keyStore != null && username != null) {
×
606

607
            try {
608

609
                KeyStore.PrivateKeyEntry privateKeyEntry = createUserKeyPair(username);
×
610

611
                if (password == null) {
×
612
                    return null;
×
613
                }
614

615
                PasswordHash localAuthHash = DrishtiApplication.getInstance().credentialsProvider().generateLocalAuthCredentials(password);
×
616
                if (localAuthHash == null) {
×
617
                    return null;
×
618
                }
619

620
                if (privateKeyEntry != null) {
×
621

622
                    // Save the encrypted secret key for local login
623
                    String encryptedLocalAuthHash = encryptString(privateKeyEntry, localAuthHash.getPassword());
×
624
                    bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD, encryptedLocalAuthHash);
×
625
                    bundle.putString(AccountHelper.INTENT_KEY.ACCOUNT_LOCAL_PASSWORD_SALT, Base64.encodeToString(localAuthHash.getSalt(), Base64.DEFAULT));
×
626

627
                    //Save db credentials
628
                    if (CredentialsHelper.shouldMigrate() || !username.equals(allSharedPreferences.fetchPioneerUser())) {
×
629

630
                        byte[] passphrase = DrishtiApplication.getInstance().credentialsProvider().generateDBCredentials(SecurityHelper.toChars(localAuthHash.getPassword()), userInfo);
×
631
                        byte[] oldPassword = allSharedPreferences.getDBEncryptionVersion() == 0 ? getGroupId(username) : DrishtiApplication.getInstance().credentialsProvider().getCredentials(username, CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH);
×
632

633
                        if (oldPassword != null && !Arrays.equals(passphrase, oldPassword)) {
×
634
                            try {
635

636
                                DrishtiApplication.getInstance().getRepository().getReadableDatabase(SecurityHelper.toChars(oldPassword)).changePassword(SecurityHelper.toChars(passphrase));
×
637
                                DrishtiApplication.getInstance().credentialsProvider().saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, encryptString(privateKeyEntry, passphrase), username);
×
638

639
                            } catch (Exception e) {
×
640
                                Timber.e("Database encryption migration to version %s failed!!! ", BuildConfig.DB_ENCRYPTION_VERSION);
×
641
                                Timber.e(e);
×
642
                            }
×
643

644
                        } else {
645

646
                            DrishtiApplication.getInstance().credentialsProvider().saveCredentials(CredentialsHelper.CREDENTIALS_TYPE.DB_AUTH, encryptString(privateKeyEntry, passphrase), username);
×
647
                        }
648
                    }
649

650
                    //Save pioneer user
651
                    if (StringUtils.isBlank(allSharedPreferences.fetchPioneerUser())) {
×
652
                        allSharedPreferences.savePioneerUser(username);
×
653
                    }
654

655
                }
656

657
            } catch (Exception e) {
×
658
                Timber.e(e);
×
659
            } finally {
660

661
                SecurityHelper.clearArray(password);
×
662
            }
663
        }
664

665
        return bundle;
×
666
    }
667

668
    public boolean hasARegisteredUser() {
669
        return !allSharedPreferences.fetchRegisteredANM().equals("");
×
670
    }
671

672
    public void logout() {
673
        logoutSession();
1✔
674
        allSettings.registerANM("");
1✔
675
        allSettings.savePreviousFetchIndex("0");
1✔
676
        configuration.getDrishtiApplication().getRepository().deleteRepository();
1✔
677
    }
1✔
678

679
    public void logoutSession() {
680
        session().expire();
1✔
681
        clearApplicationPasswordReference();
1✔
682
        ON_LOGOUT.notifyListeners(true);
1✔
683
    }
1✔
684

685
    private void clearApplicationPasswordReference() {
686

687
        SecurityHelper.clearArray(configuration.getDrishtiApplication().getPassword());
1✔
688
        configuration.getDrishtiApplication().setPassword(null);
1✔
689
    }
1✔
690

691
    public boolean hasSessionExpired() {
692
        return session().hasExpired();
1✔
693
    }
694

695
    protected void setupContextForLogin(byte[] password) {
696
        session().start(session().lengthInMilliseconds());
×
697
        configuration.getDrishtiApplication().setPassword(password);
×
698
        session().setPassword(password);
×
699
    }
×
700

701
    protected Session session() {
702
        return session;
1✔
703
    }
704

705
    public String switchLanguagePreference() {
706
        String preferredLocale = allSharedPreferences.fetchLanguagePreference();
1✔
707
        if (ENGLISH_LOCALE.equals(preferredLocale)) {
1✔
708
            allSharedPreferences.saveLanguagePreference(KANNADA_LOCALE);
1✔
709
            return KANNADA_LANGUAGE;
1✔
710
        } else {
711
            allSharedPreferences.saveLanguagePreference(ENGLISH_LOCALE);
1✔
712
            return ENGLISH_LANGUAGE;
1✔
713
        }
714
    }
715

716
    private KeyStore.PrivateKeyEntry getUserKeyPair(final String username) throws Exception {
717
        if (keyStore.containsAlias(username)) {
×
718
            return (KeyStore.PrivateKeyEntry) keyStore.getEntry(username, null);
×
719
        }
720

721
        return null;
×
722
    }
723

724
    /**
725
     * Creates a keypair for the provided username
726
     *
727
     * @param username The username to create the keypair for
728
     * @return {@link java.security.KeyStore.PrivateKeyEntry} corresponding to the user or NULL if
729
     * a problem occurred
730
     * @throws Exception
731
     */
732
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
733
    private KeyStore.PrivateKeyEntry createUserKeyPair(final String username) throws Exception {
734
        if (!keyStore.containsAlias(username)) {
×
735
            if (!keyStore.containsAlias(username)) {
×
736
                // Create the alias for the user
737
                Calendar now = Calendar.getInstance();
×
738
                Calendar expiry = Calendar.getInstance();
×
739
                expiry.add(Calendar.YEAR, 1);
×
740

741
                int serialNumber = Math.abs(0 + (int) (Math.random() * (Integer.MAX_VALUE + 1)));
×
742

743
                KeyPairGeneratorSpec generatorSpec = new KeyPairGeneratorSpec.Builder(
×
744
                        DrishtiApplication.getInstance()).setAlias(username)
×
745
                        .setSubject(new X500Principal("CN=" + username + ", O=OpenSRP"))
×
746
                        .setStartDate(now.getTime()).setEndDate(expiry.getTime())
×
747
                        .setSerialNumber(BigInteger.valueOf((long) serialNumber)).build();
×
748

749
                KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", KEYSTORE);
×
750
                generator.initialize(generatorSpec);
×
751
                generator.generateKeyPair();
×
752
            }
753
        }
754

755
        return getUserKeyPair(username);
×
756
    }
757

758
    /**
759
     * Decrypts a string using the provided keypair
760
     *
761
     * @param privateKeyEntry Keypair to use to decrypt the string
762
     * @param cipherText      String to be decrypted
763
     * @return char array of text derived from the cipher text
764
     * @throws Exception
765
     */
766
    @VisibleForTesting
767
    protected byte[] decryptString(KeyStore.PrivateKeyEntry privateKeyEntry, String cipherText) throws Exception {
768

769
        Cipher output;
770
        if (Build.VERSION.SDK_INT >= 23) {
×
771
            output = Cipher.getInstance(CIPHER);
×
772
            output.init(Cipher.DECRYPT_MODE, privateKeyEntry.getPrivateKey());
×
773
        } else {
774
            output = Cipher.getInstance(CIPHER, CIPHER_PROVIDER);
×
775
            RSAPrivateKey privateKey = (RSAPrivateKey) privateKeyEntry.getPrivateKey();
×
776
            output.init(Cipher.DECRYPT_MODE, privateKey);
×
777
        }
778

779
        CipherInputStream cipherInputStream = new CipherInputStream(
×
780
                new ByteArrayInputStream(Base64.decode(cipherText, Base64.DEFAULT)), output);
×
781

782
        ArrayList<Byte> values = new ArrayList<>();
×
783
        int nextByte;
784
        while ((nextByte = cipherInputStream.read()) != -1) {
×
785
            values.add((byte) nextByte);
×
786
        }
787

788
        byte[] bytes = new byte[values.size()];
×
789
        for (int i = 0; i < bytes.length; i++) {
×
790
            bytes[i] = values.get(i);
×
791
        }
792

793
        return bytes;
×
794
    }
795

796
    /**
797
     * Encrypts a string using the provided keypair
798
     *
799
     * @param privateKeyEntry The keypair to use to encrypt the text
800
     * @param plainTextBytes  The plain text to encrypt (should be at most 256bytes)
801
     * @return Cipher text corresponding to the plain text
802
     * @throws Exception
803
     */
804
    private String encryptString(KeyStore.PrivateKeyEntry privateKeyEntry, byte[] plainTextBytes)
805
            throws Exception {
806
        RSAPublicKey publicKey = (RSAPublicKey) privateKeyEntry.getCertificate().getPublicKey();
×
807

808
        Cipher input;
809
        if (Build.VERSION.SDK_INT >= 23) {
×
810
            input = Cipher.getInstance(CIPHER);
×
811
        } else {
812
            input = Cipher.getInstance(CIPHER, CIPHER_PROVIDER);
×
813
        }
814
        input.init(Cipher.ENCRYPT_MODE, publicKey);
×
815

816
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
×
817
        CipherOutputStream cipherOutputStream = new CipherOutputStream(outputStream, input);
×
818
        cipherOutputStream.write(plainTextBytes);
×
819
        cipherOutputStream.close();
×
820

821
        byte[] vals = outputStream.toByteArray();
×
822

823
        return Base64.encodeToString(vals, Base64.DEFAULT);
×
824
    }
825

826
    public KeyStore getKeyStore() {
827
        return keyStore;
1✔
828
    }
829

830
    @Deprecated
831
    public byte[] getGroupId(String userName) {
832
        if (keyStore != null && userName != null) {
×
833
            try {
834
                KeyStore.PrivateKeyEntry privateKeyEntry = getUserKeyPair(userName);
×
835
                return getGroupId(userName, privateKeyEntry);
×
836
            } catch (Exception e) {
×
837
                Timber.e(e);
×
838
            }
839
        }
840
        return null;
×
841
    }
842

843
    @Deprecated
844
    public byte[] getGroupId(String userName, KeyStore.PrivateKeyEntry privateKeyEntry) {
845
        if (privateKeyEntry != null) {
×
846
            String encryptedGroupId = allSharedPreferences.fetchEncryptedGroupId(userName);
×
847
            if (encryptedGroupId != null) {
×
848
                try {
849
                    return decryptString(privateKeyEntry, encryptedGroupId);
×
850
                } catch (Exception e) {
×
851
                    Timber.e(e);
×
852
                }
853
            }
854
        }
855
        return null;
×
856
    }
857

858
    public void saveUserId(String userName, String baseEntityId) {
859
        if (userName != null) {
1✔
860
            allSharedPreferences.saveUserId(userName, baseEntityId);
1✔
861
        }
862
    }
1✔
863
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc