Skip to content

Commit

Permalink
Merge pull request #688 from OpenSRP/encrypt-shared-preferences
Browse files Browse the repository at this point in the history
Encrypt Shared Preferences
  • Loading branch information
qiarie authored Feb 9, 2021
2 parents b78069d + a1e7d1e commit 4dae553
Show file tree
Hide file tree
Showing 16 changed files with 160 additions and 92 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
[![Dristhi](opensrp-app/res/drawable-mdpi/login_logo.png)](https://smartregister.atlassian.net/wiki/dashboard.action)


# Table of Contents
# Table of Contents

* [Introduction](#introduction)
* [Features](#features)
Expand Down Expand Up @@ -75,7 +75,7 @@ This section will provide a brief description on how to build and install the ap
* Install VirtualBox
* Download Genymotion & Install it
* Sign in to the genymotion app
* Create a new Genymotion Virtual Device
* Create a new Genymotion Virtual Device
* **Preferrable & Stable Choice** - API 22(Android 5.1.0), Screen size of around 800 X 1280, 1024 MB Memory --> eg. Google Nexus 7, Google Nexus 5

## How to install
Expand Down Expand Up @@ -173,7 +173,7 @@ This app provides the following networking capabilities:
Class | Represents
----- | --------------
`OpensrpSSLHelper` | SSL Connection helper
`OpenSRPImageLoader` | Asynchronous image downloder
`OpenSRPImageLoader` | Asynchronous image downloader
`HttpAgent` | Synchronous networking class with username\password ([Basic Auth](https://tools.ietf.org/html/rfc2617)) access support
`ConnectivityChangeReceiver` | Network status detection by a broadcast receiver
`GZipEncodingHttpClient` | GZip encoding and decoding capabilities
Expand Down Expand Up @@ -221,7 +221,7 @@ Class | Represents
`ServiceProvided` | It represents a service that was provided to a patient
`TimelineEvent` | It represents an event within a patient's life eg. birth

The domain object classes can be found in `org.smartregister.domain`. There are several domains namely: global domain, form and database domain.
The domain object classes can be found in `org.smartregister.domain`. There are several domains namely: global domain, form and database domain.


## 5. Sync
Expand All @@ -233,7 +233,7 @@ This app provides the following sync capabilities:

The sync classes can be found in `org.smartregister.sync`

## 6. Utitiles
## 6. Utilities

This app provides the following utilities:

Expand Down Expand Up @@ -304,7 +304,7 @@ You can quickly bootstrap view generation that take the format of a `Register` o
that implements basic register functionality such as searching, listing, sorting and counting number of records on the generic base view whilst reading a `cursor` object.

For views that display a generic List of items but require heavier customization, using the `org.smartregister.view.fragment.BaseListFragment<T>` allows you to render any generic list while providing a context
aware background executor and error handling that only requires provisioning or `Callable` function to act as a data source.
aware background executor and error handling that only requires provisioning or `Callable` function to act as a data source.
Check the Sample app's `ReportFragment` that consumes a `Retrofit` response and renders a list of objects.

## Configurability
Expand All @@ -318,3 +318,4 @@ By placing a file named `app.properties` in your implementation assets folder (S
| `system.toaster.centered` | Boolean | false | Position toaster(s) at the center of the view(s) |
| `disable.location.picker.view` | Boolean | false | Disables LocationPicker View |
| `location.picker.tag.shown` | Boolean | false | Hides/Shows the location tag in the location picker tree view |
| `encrypt.shared.preferences` | Boolean | false | Enable/disables encrypting SharedPreferences |
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
VERSION_NAME=4.2.2-SNAPSHOT
VERSION_NAME=4.2.3-SNAPSHOT
VERSION_CODE=1
GROUP=org.smartregister
POM_SETTING_DESCRIPTION=OpenSRP Client Core Application
Expand Down
4 changes: 3 additions & 1 deletion opensrp-app/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.smartregister"
android:installLocation="auto"
android:versionCode="32"
android:versionName="3.0.1">

<uses-sdk tools:overrideLibrary="androidx.security" />

<uses-feature android:name="android.hardware.camera.autofocus" />

<uses-permission android:name="android.permission.CAMERA" />
Expand Down Expand Up @@ -152,6 +155,5 @@
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />


</application>
</manifest>
10 changes: 6 additions & 4 deletions opensrp-app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -178,11 +178,11 @@ dependencies {
implementation 'org.mozilla:rhino:1.7R4'
implementation 'com.ocpsoft:ocpsoft-pretty-time:1.0.7'
api 'joda-time:joda-time:2.10.5'
implementation ('com.github.bmelnychuk:atv:1.2.9'){
implementation('com.github.bmelnychuk:atv:1.2.9') {
exclude group: 'com.google.android', module: 'android'
}

implementation ('com.github.johnkil.print:print:1.3.1'){
implementation('com.github.johnkil.print:print:1.3.1') {
exclude group: 'com.google.android', module: 'android'
}

Expand All @@ -202,17 +202,19 @@ dependencies {

implementation 'com.cloudant:cloudant-http:2.7.0'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
implementation 'androidx.security:security-crypto:1.0.0-rc03'

implementation('com.google.android.material:material:1.1.0') {
exclude group: 'com.android.support', module: 'recyclerview-v7'
}

implementation ('com.evernote:android-job:1.2.6'){
implementation('com.evernote:android-job:1.2.6') {
exclude group: 'com.google.android', module: 'android'
}

implementation group: 'commons-validator', name: 'commons-validator', version: '1.6'
implementation ('de.hdodenhof:circleimageview:3.1.0'){
implementation('de.hdodenhof:circleimageview:3.1.0') {
exclude group: 'com.google.android', module: 'android'
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ public static class PROPERTY {
public static final String SYSTEM_TOASTER_CENTERED = "system.toaster.centered";
public static final String DISABLE_LOCATION_PICKER_VIEW = "disable.location.picker.view";
public static final String LOCATION_PICKER_TAG_SHOWN = "location.picker.tag.shown";

public static final String ENCRYPT_SHARED_PREFERENCES = "encrypt.shared.preferences";
}

public interface FORCED_LOGOUT {
Expand Down
85 changes: 63 additions & 22 deletions opensrp-app/src/main/java/org/smartregister/Context.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package org.smartregister;

import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;

import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKeys;

import org.apache.commons.lang3.StringUtils;
import org.json.JSONArray;
Expand Down Expand Up @@ -116,11 +121,12 @@

import timber.log.Timber;

import static android.preference.PreferenceManager.getDefaultSharedPreferences;
import static androidx.preference.PreferenceManager.getDefaultSharedPreferences;

public class Context {

///////////////////common bindtypes///////////////

public static ArrayList<CommonRepositoryInformationHolder> bindtypes;
private static Context context = new Context();
protected DristhiConfiguration configuration;
Expand Down Expand Up @@ -223,7 +229,10 @@ public class Context {
private ClientFormRepository clientFormRepository;
private ClientRelationshipRepository clientRelationshipRepository;

private static final String SHARED_PREFERENCES_FILENAME = "%s_preferences";

/////////////////////////////////////////////////

protected Context() {
}

Expand Down Expand Up @@ -533,31 +542,31 @@ public HTTPAgent httpAgent() {

public ArrayList<DrishtiRepository> sharedRepositories() {
assignbindtypes();
ArrayList<DrishtiRepository> drishtireposotorylist = new ArrayList<DrishtiRepository>();
drishtireposotorylist.add(settingsRepository());
drishtireposotorylist.add(alertRepository());
drishtireposotorylist.add(eligibleCoupleRepository());
drishtireposotorylist.add(childRepository());
drishtireposotorylist.add(timelineEventRepository());
drishtireposotorylist.add(motherRepository());
drishtireposotorylist.add(reportRepository());
drishtireposotorylist.add(formDataRepository());
drishtireposotorylist.add(serviceProvidedRepository());
drishtireposotorylist.add(formsVersionRepository());
drishtireposotorylist.add(imageRepository());
drishtireposotorylist.add(detailsRepository());
ArrayList<DrishtiRepository> drishtiRepositoryList = new ArrayList<DrishtiRepository>();
drishtiRepositoryList.add(settingsRepository());
drishtiRepositoryList.add(alertRepository());
drishtiRepositoryList.add(eligibleCoupleRepository());
drishtiRepositoryList.add(childRepository());
drishtiRepositoryList.add(timelineEventRepository());
drishtiRepositoryList.add(motherRepository());
drishtiRepositoryList.add(reportRepository());
drishtiRepositoryList.add(formDataRepository());
drishtiRepositoryList.add(serviceProvidedRepository());
drishtiRepositoryList.add(formsVersionRepository());
drishtiRepositoryList.add(imageRepository());
drishtiRepositoryList.add(detailsRepository());
for (int i = 0; i < bindtypes.size(); i++) {
drishtireposotorylist.add(commonrepository(bindtypes.get(i).getBindtypename()));
drishtiRepositoryList.add(commonrepository(bindtypes.get(i).getBindtypename()));
}
return drishtireposotorylist;
return drishtiRepositoryList;

}

public DrishtiRepository[] sharedRepositoriesArray() {
ArrayList<DrishtiRepository> drishtiRepositories = sharedRepositories();
DrishtiRepository[] drishtireposotoryarray = drishtiRepositories
DrishtiRepository[] drishtiRepositoryArray = drishtiRepositories
.toArray(new DrishtiRepository[drishtiRepositories.size()]);
return drishtireposotoryarray;
return drishtiRepositoryArray;
}

public AllEligibleCouples allEligibleCouples() {
Expand All @@ -584,12 +593,44 @@ public AllSettings allSettings() {

public AllSharedPreferences allSharedPreferences() {
if (allSharedPreferences == null) {
allSharedPreferences = new AllSharedPreferences(
getDefaultSharedPreferences(this.applicationContext));
allSharedPreferences = new AllSharedPreferences(createSharedPreferences(this.applicationContext));
}
return allSharedPreferences;
}

private SharedPreferences createSharedPreferences(android.content.Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
&& Utils.getBooleanProperty(AllConstants.PROPERTY.ENCRYPT_SHARED_PREFERENCES)) {

return createEncryptedSharedPreferences(context);
} else {
return getDefaultSharedPreferences(context);
}
}

private SharedPreferences createEncryptedSharedPreferences(android.content.Context context) {
SharedPreferences sharedPreferences = null;

try {
String masterKeyAlias = MasterKeys.getOrCreate(MasterKeys.AES256_GCM_SPEC);

sharedPreferences = EncryptedSharedPreferences.create(
String.format(SHARED_PREFERENCES_FILENAME, context.getPackageName()),
masterKeyAlias,
context,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
} catch (Exception e) {
Timber.e(e, "Error creating encrypted SharedPreferences");

// fall back to unencrypted SharedPreferences
sharedPreferences = getDefaultSharedPreferences(context);
}

return sharedPreferences;
}

public AllBeneficiaries allBeneficiaries() {
if (allBeneficiaries == null) {
allBeneficiaries = new AllBeneficiaries(motherRepository(), childRepository(),
Expand Down Expand Up @@ -1141,7 +1182,7 @@ public EventClientRepository getForeignEventClientRepository() {
return foreignEventClientRepository;
}

public boolean hasForeignEvents(){
public boolean hasForeignEvents() {
return DrishtiApplication.getInstance().getP2PClassifier() != null;
}

Expand Down Expand Up @@ -1202,4 +1243,4 @@ public ClientRelationshipRepository getClientRelationshipRepository() {
}

///////////////////////////////////////////////////////////////////////////////
}
}
55 changes: 54 additions & 1 deletion opensrp-app/src/main/java/org/smartregister/CoreLibrary.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import android.accounts.AccountManager;
import android.accounts.OnAccountsUpdateListener;
import android.content.Intent;
import android.content.SharedPreferences;
import android.text.TextUtils;

import androidx.annotation.Nullable;
Expand All @@ -26,9 +27,12 @@
import org.smartregister.util.CredentialsHelper;
import org.smartregister.util.Utils;

import java.util.Map;
import java.util.Set;

import timber.log.Timber;

import static android.preference.PreferenceManager.getDefaultSharedPreferences;
import static androidx.preference.PreferenceManager.getDefaultSharedPreferences;

/**
* Created by keyman on 31/07/17.
Expand All @@ -52,6 +56,9 @@ public class CoreLibrary implements OnAccountsUpdateListener {

private AccountAuthenticatorXml authenticatorXml;

private static String ENCRYPTED_PREFS_KEY_KEYSET = "__androidx_security_crypto_encrypted_prefs_key_keyset__";
private static String ENCRYPTED_PREFS_VALUE_KEYSET = "__androidx_security_crypto_encrypted_prefs_value_keyset__";

public static void init(Context context) {
init(context, null);
}
Expand All @@ -68,6 +75,7 @@ public static void init(Context context, SyncConfiguration syncConfiguration, lo
if (instance == null) {
instance = new CoreLibrary(context, syncConfiguration, options);
buildTimeStamp = buildTimestamp;
upgradeSharedPreferences();
checkPlatformMigrations();
}
}
Expand All @@ -84,6 +92,51 @@ private static void checkPlatformMigrations() {
instance.context().userService().getAllSharedPreferences().migratePassphrase();
}

/**
* Check encrypted prefs sync configuration
* If configured to encrypt and there are previously saved shared prefs, recreate them encrypted
* If configured not to encrypt but previous version encrypted, clear the prefs
*/
private static void upgradeSharedPreferences() {
android.content.Context appContext = instance.context().applicationContext();
SharedPreferences existingPrefs = appContext.getSharedPreferences(appContext.getPackageName() + "_preferences", android.content.Context.MODE_PRIVATE);
Map<String, ?> entries = existingPrefs.getAll();
existingPrefs.edit().clear().apply();

// check the version of SharedPreferences (encrypted vs unencrypted)
if (Utils.getBooleanProperty(AllConstants.PROPERTY.ENCRYPT_SHARED_PREFERENCES)
&& !entries.containsKey(ENCRYPTED_PREFS_KEY_KEYSET)
&& !entries.containsKey(ENCRYPTED_PREFS_VALUE_KEYSET)) {

// create the new instance
SharedPreferences newPrefs = instance.context().allSharedPreferences().getPreferences();

copySharedPreferences(entries, newPrefs);
}
}

private static void copySharedPreferences(Map<String, ?> entries, SharedPreferences preferences) {
try {
for (Map.Entry<String, ?> entry : entries.entrySet()) {
if (entry.getValue() instanceof Boolean) {
preferences.edit().putBoolean(entry.getKey(), (Boolean) entry.getValue()).apply();
} else if (entry.getValue() instanceof Float) {
preferences.edit().putFloat(entry.getKey(), (Float) entry.getValue()).apply();
} else if (entry.getValue() instanceof Integer) {
preferences.edit().putInt(entry.getKey(), (Integer) entry.getValue()).apply();
} else if (entry.getValue() instanceof Long) {
preferences.edit().putLong(entry.getKey(), (Long) entry.getValue()).apply();
} else if (entry.getValue() instanceof String) {
preferences.edit().putString(entry.getKey(), (String) entry.getValue()).apply();
} else if (entry.getValue() instanceof Set) {
preferences.edit().putStringSet(entry.getKey(), (Set) entry.getValue()).apply();
}
}
} catch (Exception e) {
Timber.e(e, "Failed to save SharedPreference");
}
}

public static CoreLibrary getInstance() {
if (instance == null) {
throw new IllegalStateException(" Instance does not exist!!! Call "
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ public void setReadTimeout(int readTimeout) {
this.readTimeout = readTimeout;
}


/**
* This method control if POST of GET HTTP method is used to sync clients and events
*
Expand Down Expand Up @@ -187,5 +186,4 @@ public int getMaxAuthenticationRetries() {
public boolean firebasePerformanceMonitoringEnabled() {
return false;
}

}
Loading

0 comments on commit 4dae553

Please sign in to comment.